Skip to content

Commit 47b41f3

Browse files
committed
book: Chapter 3 why-first pass (since markers + std::function motivation)
Add 'since C++NN' markers to all Chapter 3 features, and give std::function a motivation lead explaining the problem it solves (uniformly storing/passing the many kinds of callable). The lambda, move-semantics, and value-category sections already motivate well.
1 parent 6583be1 commit 47b41f3

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

book/en-us/03-runtime.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ So anonymous functions are almost standard in modern programming languages.
1616

1717
### Basics
1818

19+
*(since C++11)*
20+
1921
The basic syntax of a Lambda expression is as follows:
2022

2123
```
@@ -124,6 +126,8 @@ initialize it in the expression.
124126

125127
### Generic Lambda
126128

129+
*(since C++14)*
130+
127131
In the previous section, we mentioned that the `auto` keyword cannot be used
128132
in the parameter list because it would conflict with the functionality of the template.
129133
But lambda expressions are not regular functions, without further specification on the typed parameter list, lambda expressions cannot utilize templates. Fortunately, this trouble
@@ -149,6 +153,10 @@ This part of the content is also very important, so put it here for the introduc
149153

150154
### `std::function`
151155

156+
*(since C++11)*
157+
158+
In C++, "things that can be called" come in many shapes — ordinary functions, function pointers, lambda expressions, and any object that overloads `operator()` — and they all have different types, which makes them hard to store and pass around uniformly. `std::function` exists precisely to solve this: it is a type-safe "container for callables" that can uniformly store, copy, and invoke any callable target, letting us handle "functions" as ordinary objects.
159+
152160
The essence of a Lambda expression is an object of a class type (called a closure type)
153161
that is similar to a function object type (called a closure object).
154162
When the capture list of a Lambda expression is empty, the closure object
@@ -206,6 +214,8 @@ int main() {
206214

207215
### `std::bind` and `std::placeholder`
208216

217+
*(since C++11)*
218+
209219
And `std::bind` is used to bind the parameters of the function call.
210220
It solves the requirement that we may not always be able to get all the parameters
211221
of a function at one time. Through this function, we can Part of the call parameters
@@ -331,6 +341,8 @@ This is the move semantics we will mention later.
331341

332342
### rvalue reference and lvalue reference
333343

344+
*(since C++11)*
345+
334346
To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`,
335347
where `T` is the type.
336348
The statement of the rvalue reference extends the lifecycle of this temporary value,
@@ -413,6 +425,8 @@ The reason is simple because Fortran needs it.
413425
414426
### Move semantics
415427
428+
*(since C++11)*
429+
416430
Traditional C++ has designed the concept of copy/copy for class objects
417431
through copy constructors and assignment operators,
418432
but to implement the movement of resources,
@@ -499,6 +513,8 @@ int main() {
499513

500514
### Perfect forwarding
501515

516+
*(since C++11)*
517+
502518
As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue.
503519
This creates problems for us to parameterize (pass):
504520

@@ -639,6 +655,8 @@ Because when `auto` is pushed to a different lvalue and rvalue reference, the co
639655
640656
### Guaranteed copy elision
641657
658+
*(since C++17)*
659+
642660
Before C++17, when an object was initialized from a prvalue of the same type, the compiler *was permitted* (but not required) to omit the copy/move construction — this is known as copy elision. Because it was merely allowed, the object's type still had to have an accessible copy or move constructor, even though it would not actually be called.
643661
644662
C++17 makes copy elision **guaranteed** in this case: when an object is initialized from a prvalue of the same type, there is no temporary object at all — the object is constructed directly in its target location. As a result, returning a prvalue by value is well-formed even for a type that is neither copyable nor movable:

book/zh-cn/03-runtime.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Lambda 表达式是现代 C++ 中最重要的特性之一,而 Lambda 表达式
1616

1717
### 基础
1818

19+
*(C++11)*
20+
1921
Lambda 表达式的基本语法如下:
2022

2123
```
@@ -106,6 +108,8 @@ void lambda_expression_capture() {
106108

107109
### 泛型 Lambda
108110

111+
*(C++14)*
112+
109113
上一节中我们提到了 `auto` 关键字不能够用在参数表里,这是因为这样的写法会与模板的功能产生冲突。
110114
但是 Lambda 表达式并不是普通函数,所以在没有明确指明参数表类型的情况下,Lambda 表达式并不能够模板化。
111115
幸运的是,这种麻烦只存在于 C++11 中,从 C++14 开始,Lambda 函数的形式参数可以使用 `auto`
@@ -127,6 +131,10 @@ add(1.1, 2.2);
127131
128132
### `std::function`
129133
134+
*(C++11)*
135+
136+
在 C++ 中,「可以被调用的东西」其实形态各异——普通函数、函数指针、Lambda 表达式,以及任何重载了 `operator()` 的对象,它们的类型彼此不同,很难用一种统一的方式来保存和传递。`std::function` 正是为解决这一问题而生:它是一个类型安全的「可调用对象容器」,能够统一地存储、复制并调用任意可调用目标,从而让我们能像处理普通对象那样去处理「函数」。
137+
130138
Lambda 表达式的本质是一个和函数对象类型相似的类类型(称为闭包类型)的对象(称为闭包对象),
131139
当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针值进行传递,例如:
132140
@@ -181,6 +189,8 @@ int main() {
181189
182190
### `std::bind` 和 `std::placeholder`
183191
192+
*(C++11)*
193+
184194
而 `std::bind` 则是用来绑定函数调用的参数的,
185195
它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,
186196
我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用。
@@ -278,6 +288,8 @@ std::vector<int> v = foo();
278288

279289
### 右值引用和左值引用
280290

291+
*(C++11)*
292+
281293
要拿到一个将亡值,就需要用到右值引用:`T &&`,其中 `T` 是类型。
282294
右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。
283295

@@ -350,6 +362,8 @@ void foo() {
350362
351363
### 移动语义
352364
365+
*(C++11)*
366+
353367
传统 C++ 通过拷贝构造函数和赋值操作符为类对象设计了拷贝/复制的概念,但为了实现对资源的移动操作,
354368
调用者必须使用先复制、再析构的方式,否则就需要自己实现移动对象的接口。
355369
试想,搬家的时候是把家里的东西直接搬到新家去,而不是将所有东西复制一份(重买)再放到新家、
@@ -429,6 +443,8 @@ int main() {
429443

430444
### 完美转发
431445

446+
*(C++11)*
447+
432448
前面我们提到了,一个声明的右值引用其实是一个左值。这就为我们进行参数转发(传递)造成了问题:
433449

434450
```cpp
@@ -564,6 +580,8 @@ constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcep
564580
565581
### 强制复制消除
566582
583+
*(C++17)*
584+
567585
在 C++17 之前,编译器对于「用一个纯右值初始化对象」这种情形 *可以*(但不强制)省略复制/移动构造,这被称为复制消除 (copy elision)。由于它只是「允许」而非「保证」,被初始化对象的类型仍然必须拥有可访问的复制或移动构造函数,即便它实际上不会被调用。
568586
569587
C++17 将这种情形下的复制消除变为 **强制 (guaranteed copy elision)**:当用一个同类型的纯右值初始化对象时,不再存在临时对象,对象被直接构造在目标位置上。因此,即便类型既不可复制也不可移动,按值返回一个纯右值也是合法的:

0 commit comments

Comments
 (0)