diff --git a/book/en-us/03-runtime.md b/book/en-us/03-runtime.md index ed29921..26fac34 100644 --- a/book/en-us/03-runtime.md +++ b/book/en-us/03-runtime.md @@ -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(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 +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 +#include + +int foo(int para) { + return para; +} + +int main() { + // std::function wraps a function that take int paremeter and returns int value + std::function func = foo; + + int important = 10; + std::function 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 foo() { + std::vector temp = {1, 2, 3, 4}; + return temp; +} + +std::vector 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 &&>(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 +#include + +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 + +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 +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); + else return b; // equal to static_cast(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 // std::cout +#include // std::move +#include // std::vector +#include // std::string + +int main() { + + std::string str = "Hello world."; + std::vector 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 +#include +void reference(int& v) { + std::cout << "lvalue reference" << std::endl; +} +void reference(int&& v) { + std::cout << "rvalue reference" << std::endl; +} +template +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 +#include +void reference(int& v) { + std::cout << "lvalue reference" << std::endl; +} +void reference(int&& v) { + std::cout << "rvalue reference" << std::endl; +} +template +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(v)); + std::cout << "static_cast param passing: "; + reference(static_cast(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 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 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(v)` is exactly the same as `static_cast(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 +constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept +{ return static_cast<_Tp&&>(__t); } + +template +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 Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/book/en-us/toc.md b/book/en-us/toc.md index 14be635..89607e0 100644 --- a/book/en-us/toc.md +++ b/book/en-us/toc.md @@ -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 diff --git a/book/zh-cn/03-runtime.md b/book/zh-cn/03-runtime.md index 008caab..848cb46 100644 --- a/book/zh-cn/03-runtime.md +++ b/book/zh-cn/03-runtime.md @@ -317,8 +317,8 @@ void foo() { } ``` -由于 int& 不能引用 double 类型的参数,因此必须产生一个临时值来保存 s 的值, -从而当 increase() 修改这个临时值时,从而调用完成后 s 本身并没有被修改。 +由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值, +从而当 `increase()` 修改这个临时值时,从而调用完成后 `s` 本身并没有被修改。 第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。 diff --git a/book/zh-cn/toc.md b/book/zh-cn/toc.md index 1b1f908..c567df0 100644 --- a/book/zh-cn/toc.md +++ b/book/zh-cn/toc.md @@ -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 右值引用 + 左值、右值的纯右值、将亡值、右值 + 右值引用和左值引用 diff --git a/code/3/3.1.cpp b/code/3/3.1.cpp deleted file mode 100644 index 7ddf0bf..0000000 --- a/code/3/3.1.cpp +++ /dev/null @@ -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 -#include - -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(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; -} diff --git a/code/3/3.1.lambda.basic.cpp b/code/3/3.1.lambda.basic.cpp new file mode 100644 index 0000000..085dadc --- /dev/null +++ b/code/3/3.1.lambda.basic.cpp @@ -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 +#include + +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(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; +} diff --git a/code/3/3.2.cpp b/code/3/3.2.function.wrap.cpp similarity index 60% rename from code/3/3.2.cpp rename to code/3/3.2.function.wrap.cpp index dd48ab1..93af4cf 100644 --- a/code/3/3.2.cpp +++ b/code/3/3.2.function.wrap.cpp @@ -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 #include -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 func = foo2; - + int important = 10; std::function 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; } diff --git a/code/3/3.3.cpp b/code/3/3.3.cpp deleted file mode 100644 index 27727f7..0000000 --- a/code/3/3.3.cpp +++ /dev/null @@ -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 -#include - -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; -} diff --git a/code/3/3.3.rvalue.cpp b/code/3/3.3.rvalue.cpp new file mode 100644 index 0000000..2406ea8 --- /dev/null +++ b/code/3/3.3.rvalue.cpp @@ -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 +#include + +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; +} diff --git a/code/3/3.4.historical.cpp b/code/3/3.4.historical.cpp new file mode 100644 index 0000000..d6010c6 --- /dev/null +++ b/code/3/3.4.historical.cpp @@ -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 + +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; +} \ No newline at end of file diff --git a/code/3/3.4.cpp b/code/3/3.5.move.semantics.cpp similarity index 58% rename from code/3/3.4.cpp rename to code/3/3.5.move.semantics.cpp index 6abeeee..194af92 100644 --- a/code/3/3.4.cpp +++ b/code/3/3.5.move.semantics.cpp @@ -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 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); - else return b; // 等价于 static_cast(b); + if(test) return a; // equal to static_cast(a); + else return b; // equal to static_cast(b); } int main() { A obj = return_rvalue(false); diff --git a/code/3/3.6.cpp b/code/3/3.6.cpp deleted file mode 100644 index 1f2d789..0000000 --- a/code/3/3.6.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// -// 3.6.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// -// 完美转发 - -#include -#include -void reference(int& v) { - std::cout << "左值引用" << std::endl; -} -void reference(int&& v) { - std::cout << "右值引用" << std::endl; -} -template -void pass(T&& v) { - std::cout << "普通传参:"; - reference(v); - std::cout << "std::move 传参:"; - reference(std::move(v)); - std::cout << "std::forward 传参:"; - reference(std::forward(v)); - -} -int main() { - std::cout << "传递右值:" << std::endl; - pass(1); - - std::cout << "传递左值:" << std::endl; - int v = 1; - pass(v); - - return 0; -} diff --git a/code/3/3.5.cpp b/code/3/3.6.move.semantics.cpp similarity index 58% rename from code/3/3.5.cpp rename to code/3/3.6.move.semantics.cpp index 76564c7..f849a35 100644 --- a/code/3/3.5.cpp +++ b/code/3/3.6.move.semantics.cpp @@ -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 // std::cout #include // std::move @@ -17,16 +16,15 @@ int main() { std::string str = "Hello world."; std::vector 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; diff --git a/code/3/3.7.perfect.forward.cpp b/code/3/3.7.perfect.forward.cpp new file mode 100644 index 0000000..f5e3949 --- /dev/null +++ b/code/3/3.7.perfect.forward.cpp @@ -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 +#include +void reference(int& v) { + std::cout << "lvalue reference" << std::endl; +} +void reference(int&& v) { + std::cout << "rvalue reference" << std::endl; +} +template +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(v)); + std::cout << "static_cast param passing: "; + reference(static_cast(v)); +} +int main() { + std::cout << "rvalue pass:" << std::endl; + pass(1); + + std::cout << "lvalue pass:" << std::endl; + int l = 1; + pass(l); + + return 0; +} diff --git a/code/3/Makefile b/code/3/Makefile new file mode 100644 index 0000000..642b9bc --- /dev/null +++ b/code/3/Makefile @@ -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 \ No newline at end of file