Skip to content

Commit 5305931

Browse files
ci fix: broken links
1 parent 5bdc0c1 commit 5305931

8 files changed

Lines changed: 14 additions & 14 deletions

File tree

documents/en/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ Starting from the most primitive index-based `for`, we saw how "traversal" was a
358358

359359
The core takeaway is one sentence: **a pair of iterators (one `begin`, one `end`) defines a range, and STL algorithms are built on top of this pair of iterators.**
360360

361-
In the next article, we'll hand this pair of iterators to STL algorithms—seeing how `std::sort`, `std::partition`, and `std::transform` work as "loop replacements," and what hard requirements they have on iterator categories (for example, why `std::sort` can't be used on `std::list`). There are also a few classic iterator pitfalls waiting for us there: iterator invalidation, mismatched `begin`/`end`, and reversed argument order. If you want to review container memory layouts first, vol3's [span: A View That Doesn't Own Data](../../../vol3-standard-library/02-span.md) and the container-related articles are excellent prerequisite reading.
361+
In the next article, we'll hand this pair of iterators to STL algorithms—seeing how `std::sort`, `std::partition`, and `std::transform` work as "loop replacements," and what hard requirements they have on iterator categories (for example, why `std::sort` can't be used on `std::list`). There are also a few classic iterator pitfalls waiting for us there: iterator invalidation, mismatched `begin`/`end`, and reversed argument order. If you want to review container memory layouts first, vol3's [span: A View That Doesn't Own Data](../../../../vol3-standard-library/02-span.md) and the container-related articles are excellent prerequisite reading.
362362

363363
<ReferenceCard title="References">
364364
<ReferenceItem

documents/en/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/01-copy-cost-and-motivation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,13 +415,13 @@ Good question. SSO means that if a string is short enough (the threshold in libs
415415

416416
But once a string exceeds the SSO threshold, `std::string` falls back to heap allocation, and the advantage of move semantics becomes fully apparent — one pointer swap vs one `malloc` + `memcpy`. Moreover, even for short strings, move semantics allows the compiler to avoid unnecessary copies in more scenarios.
417417

418-
For a complete analysis of SSO, we previously discussed it in detail in vol3's [string 深入:SSO、COW 与 resize_and_overwrite](../../../vol3-standard-library/02-string-memory-deep-dive.md), so we will not expand on it here.
418+
For a complete analysis of SSO, we previously discussed it in detail in vol3's [string 深入:SSO、COW 与 resize_and_overwrite](../../../../vol3-standard-library/02-string-memory-deep-dive.md), so we will not expand on it here.
419419

420420
## What We Have Figured Out So Far
421421

422422
Starting from the three deep copies in `swap`, we built a `MyString` class from scratch, saw exactly where the overhead of copying comes from (heap allocation + memory copying), and then used an experiment to prove that move semantics can deliver more than a 4x performance boost. The core intuition is also simple: **the temporary object is going to die anyway, so we might as well steal its resources before it dies**.
423423

424-
But "stealing" requires support at the language level — we need a mechanism to distinguish between "this thing will continue to exist" (lvalue) and "this thing is about to die" (rvalue), so the compiler knows when it is safe to steal. That is the topic of the next article — lvalues, rvalues, and the reference system. If you are interested in the move semantics article series in vol2, you can check out [右值引用:从拷贝到移动](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) first, which has a more systematic explanation.
424+
But "stealing" requires support at the language level — we need a mechanism to distinguish between "this thing will continue to exist" (lvalue) and "this thing is about to die" (rvalue), so the compiler knows when it is safe to steal. That is the topic of the next article — lvalues, rvalues, and the reference system. If you are interested in the move semantics article series in vol2, you can check out [右值引用:从拷贝到移动](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) first, which has a more systematic explanation.
425425

426426
<ReferenceCard title="参考文献">
427427
<ReferenceItem

documents/en/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/02-lvalue-rvalue-and-references.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ printf("%s\n", name.c_str()); // 安全
180180
181181
Without the const reference's lifetime extension rule, the temporary `std::string` returned by `get_name()` would be destroyed after the statement ends, and `name` would become a dangling reference. But because `const std::string&` binds to this temporary object, the compiler guarantees the temporary lives at least until `name` goes out of scope.
182182
183-
There's a subtle pitfall here, though—only the "first" reference that directly binds to the temporary object extends its lifetime; indirect binding through a reference chain doesn't count. For example, in `const std::string& r2 = name;`, `r2` binds to `name` (an lvalue), which doesn't involve a temporary object, so there's no lifetime extension. But if you have a situation involving multiple levels of indirect binding to a temporary object, you need to be careful. We discuss this in more detail in vol2's [Rvalue References: From Copy to Move](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md).
183+
There's a subtle pitfall here, though—only the "first" reference that directly binds to the temporary object extends its lifetime; indirect binding through a reference chain doesn't count. For example, in `const std::string& r2 = name;`, `r2` binds to `name` (an lvalue), which doesn't involve a temporary object, so there's no lifetime extension. But if you have a situation involving multiple levels of indirect binding to a temporary object, you need to be careful. We discuss this in more detail in vol2's [Rvalue References: From Copy to Move](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md).
184184
185185
:::warning
186186
Note: An rvalue reference `T&&` also has the effect of extending a temporary object's lifetime. `std::string&& r = get_name();` will also keep the returned temporary object alive until `r` goes out of scope. This is a commonality between rvalue references and const lvalue references—they can both bind to temporary objects and extend their lifetimes. The difference is that an rvalue reference allows you to modify the temporary object, while a const lvalue reference does not.
@@ -373,7 +373,7 @@ Looking back, the distinction between lvalues and rvalues wasn't invented out of
373373

374374
With this theoretical foundation, in the next article we can move into practice—implementing a move constructor and move assignment operator for MyString, seeing exactly how `std::move` works, and under what conditions copy elision lets us skip moving entirely.
375375

376-
If you want a more systematic explanation of rvalue references, vol2's [Rvalue References: From Copy to Move](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) is excellent supplementary material.
376+
If you want a more systematic explanation of rvalue references, vol2's [Rvalue References: From Copy to Move](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) is excellent supplementary material.
377377

378378
<ReferenceCard title="References">
379379
<ReferenceItem

documents/en/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/03-move-ops-stdmove-and-elision.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ NRVO's approach is very clever: when generating code, the compiler directly cons
244244

245245
Starting from C++17, this optimization became **mandatory** in certain scenarios <RefLink :id="4" preview="C++ Standard, [class.copy.elision] — mandatory elision in certain contexts" /> — the compiler must eliminate the copy, rather than "can eliminate it but doesn't have to." This isn't an optional optimization anymore; it's a defined behavior of the language. For historical reasons it's still called an "optimization," but it's actually a guarantee.
246246

247-
For the complete technical details of NRVO and RVO, we previously had a dedicated article in vol2: [RVO and NRVO: Compiler Return Value Optimization](../../../vol2-modern-features/ch00-move-semantics/03-rvo-nrvo.md).
247+
For the complete technical details of NRVO and RVO, we previously had a dedicated article in vol2: [RVO and NRVO: Compiler Return Value Optimization](../../../../vol2-modern-features/ch00-move-semantics/03-rvo-nrvo.md).
248248

249249
## Never Use std::move on Return Values
250250

@@ -536,7 +536,7 @@ Across three articles, we started from the three deep copies of `swap`, went thr
536536
537537
The core of a move constructor is "destructive copy" — steal the source object's resource pointer, then set the source object to a harmless state. Overload resolution automatically selects between copy and move; you don't need to make extra judgments at the call site. `std::move` doesn't move anything; it's simply a cast to an rvalue reference that enables overload resolution to select the move version. An rvalue reference parameter is an lvalue inside a function — because it has a name — so you still need `std::move` to move from it. The `return` statement is an exception to the "if it has a name, it's an lvalue" rule; the compiler automatically identifies implicitly movable return expressions. NRVO can deliver return values to the caller at zero cost — and `return std::move(temp)` prevents NRVO, so never write it that way. A moved-from object is in a "valid but unspecified" state; the only safe operations are assigning a new value or destructing it. Move constructors must be marked `noexcept` — otherwise `std::vector` will fall back to copying during reallocation, and the performance gap can be enormous.
538538
539-
If you want to dive deeper into more application scenarios of move semantics — perfect forwarding, universal references, reference collapsing — check out vol2's [Perfect Forwarding: Preserving Exact Value Category Propagation](../../../vol2-modern-features/ch00-move-semantics/04-perfect-forwarding.md). Move semantics combined with perfect forwarding form the complete foundation of modern C++ template programming.
539+
If you want to dive deeper into more application scenarios of move semantics — perfect forwarding, universal references, reference collapsing — check out vol2's [Perfect Forwarding: Preserving Exact Value Category Propagation](../../../../vol2-modern-features/ch00-move-semantics/04-perfect-forwarding.md). Move semantics combined with perfect forwarding form the complete foundation of modern C++ template programming.
540540
541541
<ReferenceCard title="References">
542542
<ReferenceItem

documents/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ int sum_rangefor(const std::vector<int>& v)
348348

349349
核心就一句话:**一对迭代器(一个 `begin`、一个 `end`)定义了一个 range,而 STL 算法就建立在这对迭代器之上。**
350350

351-
下一篇我们就把这对迭代器交给 STL 算法——看 `std::sort``std::partition``std::transform` 这些「循环的替代品」怎么用,以及它们对迭代器类别有什么硬性要求(比如 `std::sort` 为什么不能用在 `std::list` 上)。那里还有几个迭代器的经典陷阱等着我们:迭代器失效、配错 `begin`/`end`、参数顺序写反。如果你想先复习一下容器本身的内存布局,vol3 的 [span:不拥有数据的视图](../../../vol3-standard-library/02-span.md) 和容器相关文章是很好的前置阅读。
351+
下一篇我们就把这对迭代器交给 STL 算法——看 `std::sort``std::partition``std::transform` 这些「循环的替代品」怎么用,以及它们对迭代器类别有什么硬性要求(比如 `std::sort` 为什么不能用在 `std::list` 上)。那里还有几个迭代器的经典陷阱等着我们:迭代器失效、配错 `begin`/`end`、参数顺序写反。如果你想先复习一下容器本身的内存布局,vol3 的 [span:不拥有数据的视图](../../../../vol3-standard-library/02-span.md) 和容器相关文章是很好的前置阅读。
352352

353353
<ReferenceCard title="参考文献">
354354
<ReferenceItem

documents/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/01-copy-cost-and-motivation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,13 @@ int main()
405405

406406
但一旦字符串超过了 SSO 阈值,`std::string` 就会退回到堆分配,此时移动语义的优势就完全体现出来了——一次指针交换 vs 一次 `malloc` + `memcpy`。而且即使对于短字符串,移动语义也让编译器能在更多场景下省去不必要的拷贝。
407407

408-
关于 SSO 的完整分析,我们之前在 vol3 的 [string 深入:SSO、COW 与 resize_and_overwrite](../../../vol3-standard-library/02-string-memory-deep-dive.md) 中有详细讨论,这里就不展开了。
408+
关于 SSO 的完整分析,我们之前在 vol3 的 [string 深入:SSO、COW 与 resize_and_overwrite](../../../../vol3-standard-library/02-string-memory-deep-dive.md) 中有详细讨论,这里就不展开了。
409409

410410
## 到这里搞清楚了什么
411411

412412
我们从 `swap` 的三次深拷贝出发,手搓了 `MyString` 类,看清了拷贝操作的开销来源(堆分配 + 内存复制),然后用实验证明了移动语义能带来超过 4 倍的性能提升。核心直觉也很简单:**临时对象反正要死,不如在它死之前把资源偷走**
413413

414-
但"偷走"需要语言层面的支持——我们需要一种机制来区分"这个东西会一直存在"(左值)和"这个东西马上就要死了"(右值),这样编译器才知道什么时候可以安全地偷。这就是下一篇的内容——左值、右值与引用体系。如果你对 vol2 的移动语义系列文章感兴趣,可以先去看看 [右值引用:从拷贝到移动](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md),那里有更系统化的讲解。
414+
但"偷走"需要语言层面的支持——我们需要一种机制来区分"这个东西会一直存在"(左值)和"这个东西马上就要死了"(右值),这样编译器才知道什么时候可以安全地偷。这就是下一篇的内容——左值、右值与引用体系。如果你对 vol2 的移动语义系列文章感兴趣,可以先去看看 [右值引用:从拷贝到移动](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md),那里有更系统化的讲解。
415415

416416
<ReferenceCard title="参考文献">
417417
<ReferenceItem

documents/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/02-lvalue-rvalue-and-references.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ printf("%s\n", name.c_str()); // 安全
170170
171171
如果没有 const 引用的生命周期延长规则,`get_name()` 返回的临时 `std::string` 在语句结束后就会被销毁,`name` 就会变成一个悬垂引用。但因为 `const std::string&` 绑定了这个临时对象,编译器保证临时对象至少活到 `name` 离开作用域的时候。
172172
173-
不过这里有个微妙的坑——只有"第一个"直接绑定到临时对象的引用才能延长它的生命周期,通过引用链间接绑定的不行。比如 `const std::string& r2 = name;` 中 `r2` 绑定到 `name`(一个左值),不涉及临时对象,所以没有生命周期延长的问题。但如果涉及多层间接绑定临时对象的情况,就要小心了。我们在 vol2 的 [右值引用:从拷贝到移动](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) 里有更详细的讨论。
173+
不过这里有个微妙的坑——只有"第一个"直接绑定到临时对象的引用才能延长它的生命周期,通过引用链间接绑定的不行。比如 `const std::string& r2 = name;` 中 `r2` 绑定到 `name`(一个左值),不涉及临时对象,所以没有生命周期延长的问题。但如果涉及多层间接绑定临时对象的情况,就要小心了。我们在 vol2 的 [右值引用:从拷贝到移动](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) 里有更详细的讨论。
174174
175175
:::warning
176176
注意:右值引用 `T&&` 同样具有延长临时对象生命周期的效果。`std::string&& r = get_name();` 也会让返回的临时对象活到 `r` 离开作用域。这是右值引用和 const 左值引用的一个共同点——它们都能绑定到临时对象并延长其生命周期。区别在于,右值引用允许你修改这个临时对象,而 const 左值引用不允许。
@@ -363,7 +363,7 @@ void process(MyString&& s)
363363

364364
有了这些理论基础,下一篇我们就可以进入实战了——为 MyString 实现移动构造函数和移动赋值运算符,看看 `std::move` 到底是怎么工作的,以及拷贝消除(copy elision)在什么条件下可以让我们连移动都不需要。
365365

366-
如果你想要一个更系统化的右值引用讲解,vol2 的 [右值引用:从拷贝到移动](../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) 是很好的补充材料。
366+
如果你想要一个更系统化的右值引用讲解,vol2 的 [右值引用:从拷贝到移动](../../../../vol2-modern-features/ch00-move-semantics/01-rvalue-reference.md) 是很好的补充材料。
367367

368368
<ReferenceCard title="参考文献">
369369
<ReferenceItem

documents/vol10-open-lecture-notes/cppcon/2025/04-back-to-basics-move-semantics/03-move-ops-stdmove-and-elision.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ NRVO 的思路非常巧妙:编译器在生成代码时,直接把 `temp` 构
234234

235235
从 C++17 开始,这种优化在某些场景下变成了**强制性**的<RefLink :id="4" preview="C++ Standard, [class.copy.elision] — mandatory elision in certain contexts" />——编译器必须消除拷贝,而不是"可以消除但也可以不消除"。这不是一个可选的优化,而是语言的定义行为。历史原因让它还叫"优化",但实际上它已经是一种保证了。
236236

237-
关于 NRVO 和 RVO 的完整技术细节,我们之前在 vol2 有专门的文章讲解:[RVO 与 NRVO:编译器的返回值优化](../../../vol2-modern-features/ch00-move-semantics/03-rvo-nrvo.md)
237+
关于 NRVO 和 RVO 的完整技术细节,我们之前在 vol2 有专门的文章讲解:[RVO 与 NRVO:编译器的返回值优化](../../../../vol2-modern-features/ch00-move-semantics/03-rvo-nrvo.md)
238238

239239
## 千万别对返回值用 std::move
240240

@@ -526,7 +526,7 @@ public:
526526
527527
移动构造函数的核心是"破坏性拷贝"——偷走源对象的资源指针,然后把源对象置成无害状态。重载决议自动选择拷贝还是移动,你不需要在调用点做额外判断。`std::move` 不移动任何东西,它只是一个到右值引用的类型转换,使得重载决议能够选择移动版本。右值引用参数在函数内部是左值——因为它有名字——所以你仍然需要 `std::move` 才能从中移动。`return` 语句是"有名字就是左值"规则的例外,编译器会自动识别隐式可移动的返回表达式。NRVO 可以让返回值零成本到达调用方——而 `return std::move(temp)` 会阻止 NRVO,千万别这么写。移动后的对象处于"有效但未指定"的状态,唯一安全的操作是赋新值或析构。移动构造函数一定要标 `noexcept`——否则 `std::vector` 扩容时会退回拷贝,性能差距可能非常大。
528528
529-
如果你想继续深入移动语义的更多应用场景——完美转发、万能引用、引用折叠——可以看 vol2 的 [完美转发:保持值类别的精确传递](../../../vol2-modern-features/ch00-move-semantics/04-perfect-forwarding.md)。移动语义和完美转发搭配使用,才是现代 C++ 模板编程的完整基础。
529+
如果你想继续深入移动语义的更多应用场景——完美转发、万能引用、引用折叠——可以看 vol2 的 [完美转发:保持值类别的精确传递](../../../../vol2-modern-features/ch00-move-semantics/04-perfect-forwarding.md)。移动语义和完美转发搭配使用,才是现代 C++ 模板编程的完整基础。
530530
531531
<ReferenceCard title="参考文献">
532532
<ReferenceItem

0 commit comments

Comments
 (0)