book: translate ch03

see #12
This commit is contained in:
Changkun Ou
2019-07-20 12:06:37 +02:00
parent 09537ae424
commit 6238d66b11
15 changed files with 808 additions and 180 deletions

View File

@@ -6,10 +6,615 @@ order: 3
# Chapter 03: Language Runtime Enhancements
[TOC]
## 3.1 Lambda Expression
Lambda expressions are one of the most important features in modern C++, and Lambda expressions actually provide a feature like anonymous functions.
Anonymous functions are used when a function is needed, but you don't want to use a function to name a function. There are actually many, many scenes like this.
So anonymous functions are almost standard on modern programming languages.
### Basics
The basic syntax of a Lambda expression is as follows:
```
[capture list] (parameter list) mutable(optional) exception attribute -> return type {
// function body
}
```
The above grammar rules are well understood except for the things in `[catch list]`,
except that the function name of the general function is omitted.
The return value is in the form of a `->`
(we have already mentioned this in the tail return type earlier in the previous section).
The so-called capture list can be understood as a type of parameter.
The internal function body of a lambda expression cannot use variables outside
the body of the function by default.
At this time, the capture list can serve to transfer external data.
According to the behavior passed,
the capture list is also divided into the following types:
#### 1. Value capture
Similar to parameter passing, the value capture is based on the fact that
the variable can be copied, except that the captured variable is copied
when the lambda expression is created, not when it is called:
```cpp
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 1, and value == 100.
// Because copy_value has copied when its was created.
}
```
#### 2. Reference capture
Similar to a reference pass, the reference capture saves the reference and the value changes.
```cpp
void lambda_reference_capture() {
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 100, value == 100.
// Because copy_value stores reference
}
```
#### 3. Implicit capture
Manually writing a capture list is sometimes very complicated.
This mechanical work can be handled by the compiler.
At this point, you can write a `&` or `=` to the compiler to
declare the reference or value capture.
To summarize, capture provides the ability for lambda expressions
to use external values. The four most common forms of
capture lists can be:
- \[\] empty capture list
- \[name1, name2, ...\] captures a series of variables
- \[&\] reference capture, let the compiler derive the capture list by itself
- \[=\] value capture, let the compiler execute the list of derivation applications
#### 4. Expression capture
> This section needs to understand the rvalue references and smart pointers that
> will be mentioned later.
The value captures and reference captures mentioned above are variables that have been
declared in the outer scope, so these capture methods capture the lvalue
and not capture the rvalue.
C++14 gives us the convenience of allowing the captured members to be initialized
with arbitrary expressions, which allows the capture of rvalues.
The type of the captured variable being declared is judged according to the expression,
and the judgment is the same as using `auto`:
```cpp
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
```
In the above code, `important` is an exclusive pointer that cannot be caught.
At this time we need to transfer it to the rvalue and
initialize it in the expression.
### Generic Lambda
In the previous section we mentioned that the `auto` keyword cannot be used
in the parameter list because it would conflict with the functionality of the template.
But Lambda expressions are not ordinary functions, so Lambda expressions are not templated.
This has caused us some trouble: the parameter table cannot be generalized,
and the parameter table type must be clarified.
Fortunately, this trouble only exists in C++11, starting with C++14.
The formal parameters of the Lambda function can use the `auto` keyword
to generate generic meanings:
```cpp
void lambda_generic() {
auto generic = [](auto x, auto y) {
return x+y;
};
std::cout << generic(1, 2) << std::endl;
std::cout << generic(1.1, 2.2) << std::endl;
}
```
## 3.2 Function Object Wrapper
Although this part of the standard library is part of the standard library,
it enhances the runtime capabilities of the C++ language.
This part of the content is also very important, so put it here for introduction.
### `std::function`
The essence of a Lambda expression is an object of a class type (called a closure type)
that is similar to a function object type (called a closure object).
When the capture list of a Lambda expression is empty, the closure object
can also be converted to a function pointer value for delivery, for example:
```cpp
#include <iostream>
using foo = void(int); // function pointer
void functional(foo f) {
f(1);
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // call by function pointer
f(1); // call by lambda expression
return 0;
}
```
The above code gives two different forms of invocation, one is to call Lambda
as a function type, and the other is to directly call a Lambda expression.
In C++11, these concepts are unified.
The type of object that can be called is collectively called the callable type.
This type is introduced by `std::function`.
C++11 `std::function` is a generic, polymorphic function wrapper
whose instances can store, copy, and call any target entity that can be called.
It is also an existing callable to C++. A type-safe package of entities (relatively,
the call to a function pointer is not type-safe), in other words,
a container of functions. When we have a container for functions,
we can more easily handle functions and function pointers as objects. e.g:
```cpp
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function wraps a function that take int paremeter and returns int value
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
}
```
### `std::bind` and `std::placeholder`
And `std::bind` is used to bind the parameters of the function call.
It solves the requirement that we may not always be able to get all the parameters
of a function at one time. Through this function, we can Part of the call parameters
are bound to the function in advance to become a new object,
and then complete the call after the parameters are complete. e.g:
```cpp
int foo(int a, int b, int c) {
;
}
int main() {
// bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder
// for the first parameter.
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// when call bindFoo, we only need one param left
bindFoo(1);
}
```
> **Tip:** Note the magic of the `auto` keyword. Sometimes we may not be familiar
> with the return type of a function, but we can circumvent this problem by using `auto`.
## 3.3 rvalue Reference
rvalue references are one of the important features introduced by C++11
that are synonymous with Lambda expressions. Its introduction solves
a large number of historical issues in C++.
Eliminating extra overhead such as `std::vector`, `std::string`,
and making the function object container `std::function` possible.
### lvalue, rvalue, prvalue, xvalue
To understand what the rvalue reference is all about, you must have a clear
understanding of the lvalue and the rvalue.
**lvalue, left value**, as the name implies, is the value to the left of the assignment
symbol. To be precise, an lvalue is a persistent object that still exists after
an expression (not necessarily an assignment expression).
**Rvalue, right value**, the value on the right refers to the temporary object
that no longer exists after the expression ends.
In C++11, in order to introduce powerful rvalue references,
the concept of rvalue values is further divided into:
prvalue, and xvalue.
**pvalue, pure rvalue**, purely rvalue, either purely literal,
such as `10`, `true`; either the result of the evaluation is equivalent to
a literal or anonymous temporary object, for example `1+2`.
Temporary variables returned by non-references, temporary variables generated
by operation expressions, original literals, and Lambda expressions
are all pure rvalue values.
**xvalue, expiring value** is the concept proposed by C++11 to introduce
rvalue references (so in traditional C++, pure rvalue and rvalue are the same concept),
that is, A value that is destroyed but can be moved.
It would be a little hard to understand the xvalue,
let's look at the code like this:
```cpp
std::vector<int> foo() {
std::vector<int> temp = {1, 2, 3, 4};
return temp;
}
std::vector<int> v = foo();
```
In such code, as far as the traditional understanding is concerned,
the return value `temp` of the function `foo` is internally created
and then assigned to `v`, whereas when `v` gets this object, the entire `temp` is copied.
And then destroy `temp`, if this `temp` is very large, this will cause a lot of extra
overhead (this is the problem that traditional C++ has been criticized for).
In the last line, `v` is the lvalue, and the value returned by `foo()` is
the rvalue (which is also a pure rvalue).
However, `v` can be caught by other variables, and the return value generated
by `foo()` is used as a temporary value. Once copied by `v`,
it will be destroyed immediately, and cannot be obtained or modified.
The xvalue defines an behavior in which temporary values can be identified
while being able to be moved.
After C++11, the compiler did some work for us, where the lvalue `temp`
is subjected to this implicit rvalue conversion,
equivalent to `static_cast<std::vector<int> &&>(temp)`,
where `v` here moves the value returned by `foo` locally.
This is the move semantics we will mention later.
### rvalue reference and lvalue reference
To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`,
where `T` is the type.
The statement of the rvalue reference extends the lifecycle of this temporary value,
and as long as the variable is alive, the xvalue will continue to survive.
C++11 provides the `std::move` method to unconditionally convert
lvalue parameters to rvalues.
With it we can easily get a rvalue temporary object, for example:
```cpp
#include <iostream>
#include <string>
void reference(std::string& str) {
std::cout << "lvalue" << std::endl;
}
void reference(std::string&& str) {
std::cout << "rvalue" << std::endl;
}
int main()
{
std::string lv1 = "string,"; // lv1 is a lvalue
// std::string&& r1 = s1; // illegal, rvalue can't ref to lvalue
std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue
std::cout << rv1 << std::endl; // string,
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle
// lv2 += "Test"; // illegal, const ref can't be modified
std::cout << lv2 << std::endl; // string,string
std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle
rv2 += "string"; // legal, non-const reference can be modified
std::cout << rv2 << std::endl; // string,string,string,
reference(rv2); // output: lvalue
return 0;
}
```
`rv2` refers to an rvalue, but since it is a reference,
`rv2` is still an lvalue.
Note that there is a very interesting historical issue here,
let's look at the following code:
```cpp
#include <iostream>
int main() {
// int &a = std::move(1); // illegal, non-const lvalue reference cannot ref rvalue
const int &b = std::move(1); // legal, const lvalue reference can
std::cout << b << std::endl;
}
```
The first question, why not allow non-linear references to bind to non-lvalues?
This is because there is a logic error in this approach:
```cpp
void increase(int & v) {
v++;
}
void foo() {
double s = 1;
increase(s);
}
```
Since `int&` can't reference a parameter of type `double`,
you must generate a temporary value to hold the value of `s`.
Thus, when `increase()` modifies this temporary value,
`s` itself is not modified after the call is completed.
The second question, why do constant references allow binding to non-lvalues?
The reason is simple because Fortran needs it.
### Move semantics
Traditional C++ has designed the concept of copy/copy for class objects
through copy constructors and assignment operators,
but in order to implement the movement of resources,
The caller must use the method of copying and then destructing first,
otherwise you need to implement the interface of the mobile object yourself.
Imagine moving to move your home directly to your new home instead of
copying everything (rebuy) to your new home.
Throwing away (destroying) all the original things is a very anti-human thing.
Traditional C++ does not distinguish between the concepts of "mobile" and "copy",
resulting in a large amount of data copying, wasting time and space.
The appearance of rvalue references solves the confusion of these two concepts,
for example:
```cpp
#include <iostream>
class A {
public:
int *pointer;
A():pointer(new int(1)) {
std::cout << "construct" << pointer << std::endl;
}
A(A& a):pointer(new int(*a.pointer)) {
std::cout << "copy" << pointer << std::endl;
} // meaningless object copy
A(A&& a):pointer(a.pointer) {
a.pointer = nullptr;
std::cout << "move" << pointer << std::endl;
}
~A(){
std::cout << "destruct" << pointer << std::endl;
delete pointer;
}
};
// avoid compiler optimization
A return_rvalue(bool test) {
A a,b;
if(test) return a; // equal to static_cast<A&&>(a);
else return b; // equal to static_cast<A&&>(b);
}
int main() {
A obj = return_rvalue(false);
std::cout << "obj:" << std::endl;
std::cout << obj.pointer << std::endl;
std::cout << *obj.pointer << std::endl;
return 0;
}
```
In the code above:
1. First construct two `A` objects inside `return_rvalue`, and get the output of the two constructors;
2. After the function returns, it will generate a xvalue, which is referenced by the moving structure of `A` (`A(A&&)`), thus extending the life cycle, and taking the pointer in the rvalue and saving it to `obj`. In the middle, the pointer to the xvalue is set to `nullptr`, which prevents the memory area from being destroyed.
This avoids meaningless copy constructs and enhances performance.
Let's take a look at an example involving a standard library:
```cpp
#include <iostream> // std::cout
#include <utility> // std::move
#include <vector> // std::vector
#include <string> // std::string
int main() {
std::string str = "Hello world.";
std::vector<std::string> v;
// use push_back(const T&), copy
v.push_back(str);
// "str: Hello world."
std::cout << "str: " << str << std::endl;
// use push_back(const T&&), no copy
// the string will be moved to vector, and therefore std::move can reduce copy cost
v.push_back(std::move(str));
// str is empty now
std::cout << "str: " << str << std::endl;
return 0;
}
```
### Perfect forwarding
As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue.
This creates problems for us to parameterize (pass):
```cpp
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue reference" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " normal param passing: ";
reference(v);
}
int main() {
std::cout << "rvalue pass:" << std::endl;
pass(1);
std::cout << "lvalue pass:" << std::endl;
int l = 1;
pass(l);
return 0;
}
```
For `pass(1)`, although the value is the rvalue, since `v` is a reference, it is also an lvalue.
Therefore `reference(v)` will call `reference(int&)` and output lvalue.
For `pass(l)`, `l` is an lvalue, why is it successfully passed to `pass(T&&)`?
This is based on the **reference contraction rule**: In traditional C++, we are not able to continue to reference a reference type.
However,
C++ has relaxed this practice with the advent of rvalue references,
resulting in a reference collapse rule that allows us to reference references,
both lvalue and rvalue. But follow the rules below:
| Function parameter type | Argument parameter type | Post-derivation function parameter type |
| :--------: | :--------: | :-------------: |
| T& | lvalue ref | T& |
| T& | rvalue ref | T& |
| T&& | lvalue ref | T& |
| T&& | rvalue ref | T&& |
Therefore, the use of `T&&` in a template function may not be able to make an rvalue reference, and when a lvalue is passed, a reference to this function will be derived as an lvalue.
More precisely, ** no matter what type of reference the template parameter is, the template parameter can be derived as a right reference type** if and only if the argument type is a right reference.
This makes `v` a successful delivery of lvalues.
Perfect forwarding is based on the above rules. The so-called perfect forwarding is to let us pass the parameters,
Keep the original parameter type (lvalue reference keeps lvalue reference, rvalue reference keeps rvalue reference).
To solve this problem, we should use `std::forward` to forward (pass) the parameters:
```cpp
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue reference" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " normal param passing: ";
reference(v);
std::cout << " std::move param passing: ";
reference(std::move(v));
std::cout << " std::forward param passing: ";
reference(std::forward<T>(v));
std::cout << "static_cast<T&&> param passing: ";
reference(static_cast<T&&>(v));
}
int main() {
std::cout << "rvalue pass:" << std::endl;
pass(1);
std::cout << "lvalue pass:" << std::endl;
int l = 1;
pass(l);
return 0;
}
```
The outputs are:
```
rvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: rvalue reference
static_cast<T&&> param passing: rvalue reference
lvalue pass:
normal param passing: lvalue reference
std::move param passing: rvalue reference
std::forward param passing: lvalue reference
static_cast<T&&> param passing: lvalue reference
```
Regardless of whether the pass parameter is an lvalue or an rvalue, the normal pass argument will forward the argument as an lvalue.
So `std::move` will always accept an lvalue, which forwards the call to `reference(int&&)` to output the rvalue reference.
Only `std::forward` does not cause any extra copies, and ** perfectly forwards ** (passes) the arguments of the function to other functions that are called internally.
`std::forward` is the same as `std::move`, and nothing is done. `std::move` simply converts the lvalue to the rvalue.
`std::forward` is just a simple conversion of the parameters. From the point of view of the phenomenon,
`std::forward<T>(v)` is exactly the same as `static_cast<T&&>(v)`.
Readers may be curious as to why a statement can return values for two types of returns.
Let's take a quick look at the concrete implementation of `std::forward`. `std::forward` contains two overloads:
```cpp
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
template<typename _Tp>
constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
```
In this implementation, the function of `std::remove_reference` is to eliminate references in the type.
And `std::is_lvalue_reference` is used to check if the type derivation is correct, in the second implementation of `std::forward`
Check that the received value is indeed an lvalue, which in turn reflects the collapse rule.
When `std::forward` accepts an lvalue, `_Tp` is deduced to the lvalue, so the return value is the lvalue; and when it accepts the rvalue,
`_Tp` is derived as an rvalue reference, and based on the collapse rule, the return value becomes the rvalue of `&& + &&`.
It can be seen that the principle of `std::forward` is to make clever use of the differences in template type derivation.
At this point we can answer the question: Why is `auto&&` the safest way to use looping statements?
Because when `auto` is pushed to a different lvalue and rvalue reference, the collapsed combination with `&&` is perfectly forwarded.
## Conclusion
This chapter introduces the most important runtime enhancements in modern C++, and I believe that all the features mentioned in this section are worth knowing:
Lambda expression
1. Function object container std::function
2. rvalue reference
[Table of Content](./toc.md) | [Previous Chapter](./02-usability.md) | [Next Chapter: Containers](./04-containers.md)
## Further Readings
- [Bjarne Stroustrup, The Design and Evolution of C++](https://www.amazon.com/Design-Evolution-C-Bjarne-Stroustrup/dp/0201543303)
## Licenses
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png" /></a><br />This work was written by [Ou Changkun](https://changkun.de) and licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License</a>. The code of this repository is open sourced under the [MIT license](../../LICENSE).

View File

@@ -42,17 +42,13 @@
- [**Chapter 03 Language Runtime Enhancements**](./03-runtime.md)
+ 3.1 Lambda expression
+ Basics
+ Value capture
+ Reference capture
+ Implicit capture
+ Expression capture
+ Generic lambda
+ Generics
+ 3.2 Function object wrapper
+ std::function
+ std::bind/std::placeholder
+ 3.3 rvalue reference
+ lvalue, rvalue, prvalue, xvalue
+ rvalue reference & lvalue reference
+ rvalue reference and lvalue reference
+ Move semantics
+ Perfect forwarding
- [**Chapter 04 Containers**](./04-containers.md)
@@ -87,7 +83,7 @@
+ 7.4 Condition Variable
+ 7.5 Atomic Operation and Memory Model
+ Atomic Operation
+ Concistency Model
+ Consistency Model
+ Memory Orders
- [**Chapter 08 File System**](./08-filesystem.md)
+ 8.1 Documents and links

View File

@@ -317,8 +317,8 @@ void foo() {
}
```
由于 int& 不能引用 double 类型的参数,因此必须产生一个临时值来保存 s 的值,
从而当 increase() 修改这个临时值时,从而调用完成后 s 本身并没有被修改。
由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值,
从而当 `increase()` 修改这个临时值时,从而调用完成后 `s` 本身并没有被修改。
第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。

View File

@@ -41,15 +41,11 @@
- 强类型枚举
- [**第 3 章 语言运行期的强化**](./03-runtime.md)
+ 3.1 lambda 表达式
+ lambda 表达式基础
+ 值捕获
+ 引用捕获
+ 隐式捕获
+ 表达式捕获
+ 泛型 lambda
+ 基础
+ 泛型
+ 3.2 函数对象包装器
+ std::function
+ std::bind/std::placeholder
+ std::bindstd::placeholder
+ 3.3 右值引用
+ 左值、右值的纯右值、将亡值、右值
+ 右值引用和左值引用

View File

@@ -1,55 +0,0 @@
//
// 3.1.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// lambda expression
#include <iostream>
#include <utility>
void learn_lambda_func_1() {
int value_1 = 1;
auto copy_value_1 = [value_1] {
return value_1;
};
value_1 = 100;
auto stored_value_1 = copy_value_1();
std::cout << "stored_value_1=" << stored_value_1 << std::endl;
// 这时, stored_value_1 == 1, 而 value_1 == 100.
// 因为 copy_value_1 在创建时就保存了一份 value_1 的拷贝
}
void learn_lambda_func_2() {
int value_2 = 1;
auto copy_value_2 = [&value_2] {
return value_2;
};
value_2 = 100;
auto stored_value_2 = copy_value_2();
std::cout << "stored_value_2=" << stored_value_2 << std::endl;
// 这时, stored_value_2 == 100, value_2 == 100.
// 因为 copy_value_2 保存的是引用
}
int main() {
learn_lambda_func_1();
learn_lambda_func_2();
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
// 泛型 lambda
auto generic = [](auto x, auto y) {
return x+y;
};
generic(1, 2);
generic(1.1, 2.2);
return 0;
}

View File

@@ -0,0 +1,61 @@
//
// 3.1.lambda.basic.cpp
// chapter 03 runtime enhancement
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
#include <iostream>
#include <utility>
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 1, and value == 100.
// Because copy_value has copied when its was created.
}
void lambda_reference_capture() {
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// At this moment, stored_value == 100, value == 100.
// Because copy_value stores reference
}
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
void lambda_generic() {
auto generic = [](auto x, auto y) {
return x+y;
};
std::cout << generic(1, 2) << std::endl;
std::cout << generic(1.1, 2.2) << std::endl;
}
int main() {
lambda_value_capture();
lambda_reference_capture();
lambda_expression_capture();
lambda_generic();
return 0;
}

View File

@@ -1,16 +1,16 @@
//
// 3.2.cpp
// 3.2.function.wrap.cpp
// chapter 03 runtime enhancement
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// std::function std::bind
#include <functional>
#include <iostream>
using foo = void(int); // 定义函数指针
using foo = void(int); // function pointer
void functional(foo f) {
f(1);
}
@@ -24,29 +24,28 @@ int foo3(int a, int b, int c) {
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // 函数指针调用
f(1); // lambda 表达式调用
// std::function 包装了一个返回值为 int, 参数为 int 的函数
functional(f); // call by function pointer
f(1); // call by lambda expression
// std::function wraps a function that take int paremeter and returns int value
std::function<int(int)> func = foo2;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
// 将参数1,2绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
// bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder
// for the first parameter.
auto bindFoo = std::bind(foo3, std::placeholders::_1, 1,2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
// when call bindFoo, we only need one param left
bindFoo(1);
return 0;
}

View File

@@ -1,39 +0,0 @@
//
// 3.3.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// 右值引用 rvalue reference
#include <iostream>
#include <string>
void reference(std::string& str) {
std::cout << "左值" << std::endl;
}
void reference(std::string&& str) {
std::cout << "右值" << std::endl;
}
int main()
{
std::string lv1 = "string,"; // lv1 是一个左值
// std::string&& r1 = s1; // 非法, 右值引用不能引用左值
std::string&& rv1 = std::move(lv1); // 合法, std::move可以将左值转移为右值
std::cout << rv1 << std::endl; // string,
const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的申明周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
std::cout << lv2 << std::endl; // string,string
std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象声明周期
rv2 += "string"; // 合法, 非常量引用能够修改临时变量
std::cout << rv2 << std::endl; // string,string,string,
reference(rv2); // 输出左值
return 0;
}

38
code/3/3.3.rvalue.cpp Normal file
View File

@@ -0,0 +1,38 @@
//
// 3.3.rvalue.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
#include <iostream>
#include <string>
void reference(std::string& str) {
std::cout << "lvalue" << std::endl;
}
void reference(std::string&& str) {
std::cout << "rvalue" << std::endl;
}
int main()
{
std::string lv1 = "string,"; // lv1 is a lvalue
// std::string&& r1 = s1; // illegal, rvalue can't ref to lvalue
std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue
std::cout << rv1 << std::endl; // string,
const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle
// lv2 += "Test"; // illegal, const ref can't be modified
std::cout << lv2 << std::endl; // string,string
std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle
rv2 += "string"; // legal, non-const reference can be modified
std::cout << rv2 << std::endl; // string,string,string,
reference(rv2); // output: lvalue
return 0;
}

16
code/3/3.4.historical.cpp Normal file
View File

@@ -0,0 +1,16 @@
//
// 3.4.historical.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
#include <iostream>
int main() {
// int &a = std::move(1); // illegal, non-const lvalue reference cannot ref rvalue
const int &b = std::move(1); // legal, const lvalue reference can
std::cout << b << std::endl;
}

View File

@@ -1,36 +1,35 @@
//
// 3.4.cpp
// 3.5.move.semantics.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// 移动语义
#include <iostream>
class A {
public:
int *pointer;
A():pointer(new int(1)) {
std::cout << "构造" << pointer << std::endl;
std::cout << "construct" << pointer << std::endl;
}
A(A& a):pointer(new int(*a.pointer)) {
std::cout << "拷贝" << pointer << std::endl;
} // 无意义的对象拷贝
std::cout << "copy" << pointer << std::endl;
} // meaningless object copy
A(A&& a):pointer(a.pointer) {
a.pointer = nullptr;
std::cout << "移动" << pointer << std::endl;
std::cout << "move" << pointer << std::endl;
}
~A(){
std::cout << "析构" << pointer << std::endl;
std::cout << "destruct" << pointer << std::endl;
delete pointer;
}
};
// 防止编译器优化
// avoid compiler optimization
A return_rvalue(bool test) {
A a,b;
if(test) return a; // 等价于 static_cast<A&&>(a);
else return b; // 等价于 static_cast<A&&>(b);
if(test) return a; // equal to static_cast<A&&>(a);
else return b; // equal to static_cast<A&&>(b);
}
int main() {
A obj = return_rvalue(false);

View File

@@ -1,37 +0,0 @@
//
// 3.6.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// 完美转发
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "左值引用" << std::endl;
}
void reference(int&& v) {
std::cout << "右值引用" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << "普通传参:";
reference(v);
std::cout << "std::move 传参:";
reference(std::move(v));
std::cout << "std::forward 传参:";
reference(std::forward<T>(v));
}
int main() {
std::cout << "传递右值:" << std::endl;
pass(1);
std::cout << "传递左值:" << std::endl;
int v = 1;
pass(v);
return 0;
}

View File

@@ -1,11 +1,10 @@
//
// 3.5.cpp
// 3.5.move.semantics.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
// 移动语义
#include <iostream> // std::cout
#include <utility> // std::move
@@ -17,16 +16,15 @@ int main() {
std::string str = "Hello world.";
std::vector<std::string> v;
// 将使用 push_back(const T&), 即产生拷贝行为
// use push_back(const T&), copy
v.push_back(str);
// 将输出 "str: Hello world."
// "str: Hello world."
std::cout << "str: " << str << std::endl;
// 将使用 push_back(const T&&), 不会出现拷贝行为
// 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
// 这步操作后, str 中的值会变为空
// use push_back(const T&&), no copy
// the string will be moved to vector, and therefore std::move can reduce copy cost
v.push_back(std::move(str));
// 将输出 "str: "
// str is empty now
std::cout << "str: " << str << std::endl;
return 0;

View File

@@ -0,0 +1,37 @@
//
// 3.6.perfect.forward.cpp
// modern c++ tutorial
//
// created by changkun at changkun.de
// https://github.com/changkun/modern-cpp-tutorial
//
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue reference" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " normal param passing: ";
reference(v);
std::cout << " std::move param passing: ";
reference(std::move(v));
std::cout << " std::forward param passing: ";
reference(std::forward<T>(v));
std::cout << "static_cast<T&&> param passing: ";
reference(static_cast<T&&>(v));
}
int main() {
std::cout << "rvalue pass:" << std::endl;
pass(1);
std::cout << "lvalue pass:" << std::endl;
int l = 1;
pass(l);
return 0;
}

14
code/3/Makefile Normal file
View File

@@ -0,0 +1,14 @@
#
# modern cpp tutorial
#
# created by changkun at changkun.de
# https://github.com/changkun/modern-cpp-tutorial
#
all: $(patsubst %.cpp, %.out, $(wildcard *.cpp))
%.out: %.cpp Makefile
clang++ $< -o $@ -std=c++2a -pedantic
clean:
rm *.out