mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-16 20:27:08 +03:00
@@ -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).
|
||||
@@ -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
|
||||
|
||||
@@ -317,8 +317,8 @@ void foo() {
|
||||
}
|
||||
```
|
||||
|
||||
由于 int& 不能引用 double 类型的参数,因此必须产生一个临时值来保存 s 的值,
|
||||
从而当 increase() 修改这个临时值时,从而调用完成后 s 本身并没有被修改。
|
||||
由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值,
|
||||
从而当 `increase()` 修改这个临时值时,从而调用完成后 `s` 本身并没有被修改。
|
||||
|
||||
第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。
|
||||
|
||||
|
||||
@@ -41,15 +41,11 @@
|
||||
- 强类型枚举
|
||||
- [**第 3 章 语言运行期的强化**](./03-runtime.md)
|
||||
+ 3.1 lambda 表达式
|
||||
+ lambda 表达式基础
|
||||
+ 值捕获
|
||||
+ 引用捕获
|
||||
+ 隐式捕获
|
||||
+ 表达式捕获
|
||||
+ 泛型 lambda
|
||||
+ 基础
|
||||
+ 泛型
|
||||
+ 3.2 函数对象包装器
|
||||
+ std::function
|
||||
+ std::bind/std::placeholder
|
||||
+ std::bind 和 std::placeholder
|
||||
+ 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
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
@@ -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
|
||||
//
|
||||
// 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);
|
||||
@@ -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
|
||||
//
|
||||
// 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;
|
||||
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