mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-17 04:34:40 +03:00
@@ -6,10 +6,615 @@ order: 3
|
|||||||
|
|
||||||
# Chapter 03: Language Runtime Enhancements
|
# 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)
|
[Table of Content](./toc.md) | [Previous Chapter](./02-usability.md) | [Next Chapter: Containers](./04-containers.md)
|
||||||
|
|
||||||
## Further Readings
|
## Further Readings
|
||||||
|
|
||||||
|
- [Bjarne Stroustrup, The Design and Evolution of C++](https://www.amazon.com/Design-Evolution-C-Bjarne-Stroustrup/dp/0201543303)
|
||||||
|
|
||||||
## Licenses
|
## 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).
|
<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).
|
||||||
@@ -42,17 +42,13 @@
|
|||||||
- [**Chapter 03 Language Runtime Enhancements**](./03-runtime.md)
|
- [**Chapter 03 Language Runtime Enhancements**](./03-runtime.md)
|
||||||
+ 3.1 Lambda expression
|
+ 3.1 Lambda expression
|
||||||
+ Basics
|
+ Basics
|
||||||
+ Value capture
|
+ Generics
|
||||||
+ Reference capture
|
|
||||||
+ Implicit capture
|
|
||||||
+ Expression capture
|
|
||||||
+ Generic lambda
|
|
||||||
+ 3.2 Function object wrapper
|
+ 3.2 Function object wrapper
|
||||||
+ std::function
|
+ std::function
|
||||||
+ std::bind/std::placeholder
|
+ std::bind/std::placeholder
|
||||||
+ 3.3 rvalue reference
|
+ 3.3 rvalue reference
|
||||||
+ lvalue, rvalue, prvalue, xvalue
|
+ lvalue, rvalue, prvalue, xvalue
|
||||||
+ rvalue reference & lvalue reference
|
+ rvalue reference and lvalue reference
|
||||||
+ Move semantics
|
+ Move semantics
|
||||||
+ Perfect forwarding
|
+ Perfect forwarding
|
||||||
- [**Chapter 04 Containers**](./04-containers.md)
|
- [**Chapter 04 Containers**](./04-containers.md)
|
||||||
@@ -87,7 +83,7 @@
|
|||||||
+ 7.4 Condition Variable
|
+ 7.4 Condition Variable
|
||||||
+ 7.5 Atomic Operation and Memory Model
|
+ 7.5 Atomic Operation and Memory Model
|
||||||
+ Atomic Operation
|
+ Atomic Operation
|
||||||
+ Concistency Model
|
+ Consistency Model
|
||||||
+ Memory Orders
|
+ Memory Orders
|
||||||
- [**Chapter 08 File System**](./08-filesystem.md)
|
- [**Chapter 08 File System**](./08-filesystem.md)
|
||||||
+ 8.1 Documents and links
|
+ 8.1 Documents and links
|
||||||
|
|||||||
@@ -317,8 +317,8 @@ void foo() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
由于 int& 不能引用 double 类型的参数,因此必须产生一个临时值来保存 s 的值,
|
由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值,
|
||||||
从而当 increase() 修改这个临时值时,从而调用完成后 s 本身并没有被修改。
|
从而当 `increase()` 修改这个临时值时,从而调用完成后 `s` 本身并没有被修改。
|
||||||
|
|
||||||
第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。
|
第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。
|
||||||
|
|
||||||
|
|||||||
@@ -41,15 +41,11 @@
|
|||||||
- 强类型枚举
|
- 强类型枚举
|
||||||
- [**第 3 章 语言运行期的强化**](./03-runtime.md)
|
- [**第 3 章 语言运行期的强化**](./03-runtime.md)
|
||||||
+ 3.1 lambda 表达式
|
+ 3.1 lambda 表达式
|
||||||
+ lambda 表达式基础
|
+ 基础
|
||||||
+ 值捕获
|
+ 泛型
|
||||||
+ 引用捕获
|
|
||||||
+ 隐式捕获
|
|
||||||
+ 表达式捕获
|
|
||||||
+ 泛型 lambda
|
|
||||||
+ 3.2 函数对象包装器
|
+ 3.2 函数对象包装器
|
||||||
+ std::function
|
+ std::function
|
||||||
+ std::bind/std::placeholder
|
+ std::bind 和 std::placeholder
|
||||||
+ 3.3 右值引用
|
+ 3.3 右值引用
|
||||||
+ 左值、右值的纯右值、将亡值、右值
|
+ 左值、右值的纯右值、将亡值、右值
|
||||||
+ 右值引用和左值引用
|
+ 右值引用和左值引用
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
61
code/3/3.1.lambda.basic.cpp
Normal file
61
code/3/3.1.lambda.basic.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
//
|
//
|
||||||
// 3.2.cpp
|
// 3.2.function.wrap.cpp
|
||||||
|
// chapter 03 runtime enhancement
|
||||||
// modern c++ tutorial
|
// modern c++ tutorial
|
||||||
//
|
//
|
||||||
// created by changkun at changkun.de
|
// created by changkun at changkun.de
|
||||||
// https://github.com/changkun/modern-cpp-tutorial
|
// https://github.com/changkun/modern-cpp-tutorial
|
||||||
//
|
//
|
||||||
// std::function std::bind
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using foo = void(int); // 定义函数指针
|
using foo = void(int); // function pointer
|
||||||
void functional(foo f) {
|
void functional(foo f) {
|
||||||
f(1);
|
f(1);
|
||||||
}
|
}
|
||||||
@@ -28,10 +28,10 @@ int main() {
|
|||||||
auto f = [](int value) {
|
auto f = [](int value) {
|
||||||
std::cout << value << std::endl;
|
std::cout << value << std::endl;
|
||||||
};
|
};
|
||||||
functional(f); // 函数指针调用
|
functional(f); // call by function pointer
|
||||||
f(1); // lambda 表达式调用
|
f(1); // call by lambda expression
|
||||||
|
|
||||||
// std::function 包装了一个返回值为 int, 参数为 int 的函数
|
// std::function wraps a function that take int paremeter and returns int value
|
||||||
std::function<int(int)> func = foo2;
|
std::function<int(int)> func = foo2;
|
||||||
|
|
||||||
int important = 10;
|
int important = 10;
|
||||||
@@ -41,12 +41,11 @@ int main() {
|
|||||||
std::cout << func(10) << std::endl;
|
std::cout << func(10) << std::endl;
|
||||||
std::cout << func2(10) << std::endl;
|
std::cout << func2(10) << std::endl;
|
||||||
|
|
||||||
|
// bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder
|
||||||
// 将参数1,2绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
|
// for the first parameter.
|
||||||
auto bindFoo = std::bind(foo3, std::placeholders::_1, 1,2);
|
auto bindFoo = std::bind(foo3, std::placeholders::_1, 1,2);
|
||||||
// 这时调用 bindFoo 时,只需要提供第一个参数即可
|
// when call bindFoo, we only need one param left
|
||||||
bindFoo(1);
|
bindFoo(1);
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -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
38
code/3/3.3.rvalue.cpp
Normal 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
16
code/3/3.4.historical.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,36 +1,35 @@
|
|||||||
//
|
//
|
||||||
// 3.4.cpp
|
// 3.5.move.semantics.cpp
|
||||||
// modern c++ tutorial
|
// modern c++ tutorial
|
||||||
//
|
//
|
||||||
// created by changkun at changkun.de
|
// created by changkun at changkun.de
|
||||||
// https://github.com/changkun/modern-cpp-tutorial
|
// https://github.com/changkun/modern-cpp-tutorial
|
||||||
//
|
//
|
||||||
// 移动语义
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
class A {
|
class A {
|
||||||
public:
|
public:
|
||||||
int *pointer;
|
int *pointer;
|
||||||
A():pointer(new int(1)) {
|
A():pointer(new int(1)) {
|
||||||
std::cout << "构造" << pointer << std::endl;
|
std::cout << "construct" << pointer << std::endl;
|
||||||
}
|
}
|
||||||
A(A& a):pointer(new int(*a.pointer)) {
|
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(A&& a):pointer(a.pointer) {
|
||||||
a.pointer = nullptr;
|
a.pointer = nullptr;
|
||||||
std::cout << "移动" << pointer << std::endl;
|
std::cout << "move" << pointer << std::endl;
|
||||||
}
|
}
|
||||||
~A(){
|
~A(){
|
||||||
std::cout << "析构" << pointer << std::endl;
|
std::cout << "destruct" << pointer << std::endl;
|
||||||
delete pointer;
|
delete pointer;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 防止编译器优化
|
// avoid compiler optimization
|
||||||
A return_rvalue(bool test) {
|
A return_rvalue(bool test) {
|
||||||
A a,b;
|
A a,b;
|
||||||
if(test) return a; // 等价于 static_cast<A&&>(a);
|
if(test) return a; // equal to static_cast<A&&>(a);
|
||||||
else return b; // 等价于 static_cast<A&&>(b);
|
else return b; // equal to static_cast<A&&>(b);
|
||||||
}
|
}
|
||||||
int main() {
|
int main() {
|
||||||
A obj = return_rvalue(false);
|
A obj = return_rvalue(false);
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
//
|
//
|
||||||
// 3.5.cpp
|
// 3.5.move.semantics.cpp
|
||||||
// modern c++ tutorial
|
// modern c++ tutorial
|
||||||
//
|
//
|
||||||
// created by changkun at changkun.de
|
// created by changkun at changkun.de
|
||||||
// https://github.com/changkun/modern-cpp-tutorial
|
// https://github.com/changkun/modern-cpp-tutorial
|
||||||
//
|
//
|
||||||
// 移动语义
|
|
||||||
|
|
||||||
#include <iostream> // std::cout
|
#include <iostream> // std::cout
|
||||||
#include <utility> // std::move
|
#include <utility> // std::move
|
||||||
@@ -17,16 +16,15 @@ int main() {
|
|||||||
std::string str = "Hello world.";
|
std::string str = "Hello world.";
|
||||||
std::vector<std::string> v;
|
std::vector<std::string> v;
|
||||||
|
|
||||||
// 将使用 push_back(const T&), 即产生拷贝行为
|
// use push_back(const T&), copy
|
||||||
v.push_back(str);
|
v.push_back(str);
|
||||||
// 将输出 "str: Hello world."
|
// "str: Hello world."
|
||||||
std::cout << "str: " << str << std::endl;
|
std::cout << "str: " << str << std::endl;
|
||||||
|
|
||||||
// 将使用 push_back(const T&&), 不会出现拷贝行为
|
// use push_back(const T&&), no copy
|
||||||
// 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
|
// the string will be moved to vector, and therefore std::move can reduce copy cost
|
||||||
// 这步操作后, str 中的值会变为空
|
|
||||||
v.push_back(std::move(str));
|
v.push_back(std::move(str));
|
||||||
// 将输出 "str: "
|
// str is empty now
|
||||||
std::cout << "str: " << str << std::endl;
|
std::cout << "str: " << str << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
37
code/3/3.7.perfect.forward.cpp
Normal file
37
code/3/3.7.perfect.forward.cpp
Normal 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
14
code/3/Makefile
Normal 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
|
||||||
Reference in New Issue
Block a user