Skip to content

Commit 4c1026d

Browse files
release: v0.9.0 ready (#124)
* feat(vol4): 元编程子卷 concepts 三连 vol4-advanced/vol3-metaprogramming-cpp20-23 子卷前三篇: - 01 Concepts 详解(四种语法形式/enable_if 报错对比/标准库概念) - 02 约束模板(subsumption 与重载/原子约束真包含) - 03 requires 表达式(四种成分/不求值/硬错误坑) 配套 9 个示例;报错演示用 -D 宏切换。index 改正式导航。 * feat(vol10): CppCon 2025 std::optional 演讲二创六篇 Steve Downey《The Evolution of std::optional: From Boost to C++26》 拆 6 篇深度笔记,聚焦 optional<T&>(P2988) 从 2005 提出到 2025 年 Sofia 会议投票通过进 C++26 的历程。引用三重身份、assign-through 与 rebind 之争、赋值重绑定、浅层 const、移动语义陷阱、The Beman Project 标准化真相。全部 optional<T&> 代码在 GCC 16.1.1 -std=c++26 实跑,附赋值汇编实证。修正了素材的 CTAD 误断与 Beman 拼写。 接入 2025 目录页。四门禁(validate_frontmatter/check_links/ check_quality/build)全绿。 * feat(vol2): 优化 ch00/05 与 ch01 智能指针全章(de-AI+清死链+实测验证) ch00/05 移动语义实战: - 修正 benchmark 数据硬伤(原文「285ms/3倍」与实测矛盾)→ GCC16 实测重写 - 补全 noexcept 验证代码(原缺 ThrowingType,代码与输出对不上) - 清死链 code/volumn_codes/vol2/ch00-move-semantics(空目录) - 新增 2 个 OnlineCompilerDemo(push_back_emplace、noexcept_sort_vs_realloc) ch01 智能指针 6 篇 CN: - 人称统一(笔者/咱们/您)、删「## 小结」换承接、清 AI 味词、工具链 GCC16 - 修实测硬伤: · raii-deep-dive:exit 验证缺 main(给了输出却链接不过) · unique-ptr:sizeof 代码缺 StatefulDeleter 但输出里有;5 处死链 + PIMPL 死链 · weak-ptr:lock() 实测慢 ~32 倍(原文写 10-15 倍,把直接访问耗时写高了) · custom-deleter:11 处死链 + 末尾整段「## 验证代码」死链 + 535-536 重复行 · scope-guard:验证段死链 新增 code/examples/vol2: noexcept_sort_vs_realloc.cpp、move_benchmark.cpp、push_back_emplace.cpp(均 GCC16 编译验证) EN 同步 ch00/05 + ch01/01-04
1 parent 80dc435 commit 4c1026d

37 files changed

Lines changed: 2619 additions & 413 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
---
2121

2222
<!-- COVERAGE_START -->
23-
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 602/604 docs translated
23+
![English Coverage](https://img.shields.io/badge/en_coverage-98%25-green.svg) 602/614 docs translated
2424
<!-- COVERAGE_END -->
2525

2626
## 这是什么项目
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// move_benchmark.cpp -- 拷贝 vs 移动性能对比(分离构造开销)
2+
// Standard: C++17
3+
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
4+
//
5+
// 关键设计:把"构造"这一固定开销单独测出来作为 baseline,再用
6+
// (构造+拷贝) - 构造 和 (构造+移动) - 构造 得到纯粹的拷贝/移动耗时,
7+
// 避免构造开销稀释掉移动操作本身"接近零"的事实。
8+
//
9+
// 注意:绝对耗时是机器相关的,但"纯移动 ≈ 0、纯拷贝 >> 0"的结论稳定。
10+
#include <chrono>
11+
#include <iostream>
12+
#include <numeric>
13+
#include <string>
14+
#include <vector>
15+
16+
class BigData {
17+
std::vector<double> payload_;
18+
19+
public:
20+
explicit BigData(std::size_t n) : payload_(n) {
21+
std::iota(payload_.begin(), payload_.end(), 0.0);
22+
}
23+
24+
BigData(const BigData& other) : payload_(other.payload_) {}
25+
BigData(BigData&& other) noexcept = default;
26+
BigData& operator=(const BigData&) = default;
27+
BigData& operator=(BigData&&) noexcept = default;
28+
};
29+
30+
/// @brief 测量函数执行时间的辅助模板
31+
template <typename Func> double measure_ms(Func&& func, int iterations) {
32+
auto start = std::chrono::high_resolution_clock::now();
33+
for (int i = 0; i < iterations; ++i) {
34+
func();
35+
}
36+
auto end = std::chrono::high_resolution_clock::now();
37+
return std::chrono::duration<double, std::milli>(end - start).count();
38+
}
39+
40+
int main() {
41+
constexpr std::size_t kDataSize = 1000000; // 100 万个 double,约 8MB
42+
constexpr int kIterations = 100;
43+
44+
std::cout << "数据大小: " << kDataSize * sizeof(double) / 1024 << " KB\n";
45+
std::cout << "迭代次数: " << kIterations << "\n\n";
46+
47+
// 测试 0:仅构造(baseline)
48+
auto construct_time = measure_ms(
49+
[&]() {
50+
BigData source(kDataSize);
51+
(void)source;
52+
},
53+
kIterations);
54+
55+
std::cout << "仅构造(baseline): " << construct_time << " ms\n";
56+
57+
// 测试 1:构造 + 拷贝
58+
auto copy_time = measure_ms(
59+
[&]() {
60+
BigData source(kDataSize);
61+
BigData copy = source; // 拷贝构造
62+
(void)copy;
63+
},
64+
kIterations);
65+
66+
std::cout << "构造 + 拷贝: " << copy_time << " ms\n";
67+
68+
// 测试 2:构造 + 移动
69+
auto move_time = measure_ms(
70+
[&]() {
71+
BigData source(kDataSize);
72+
BigData moved = std::move(source); // 移动构造
73+
(void)moved;
74+
},
75+
kIterations);
76+
77+
std::cout << "构造 + 移动: " << move_time << " ms\n\n";
78+
79+
// 分离出纯粹的拷贝/移动耗时
80+
double actual_copy = copy_time - construct_time;
81+
double actual_move = move_time - construct_time;
82+
83+
std::cout << "=== 分离后的实际耗时 ===\n";
84+
std::cout << "纯拷贝: " << actual_copy << " ms\n";
85+
std::cout << "纯移动: " << actual_move << " ms\n";
86+
87+
if (actual_move > 0.01) {
88+
std::cout << "加速比: " << actual_copy / actual_move << "x\n";
89+
} else {
90+
std::cout << "移动耗时在测量噪声范围内(接近零)\n";
91+
}
92+
93+
return 0;
94+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// noexcept_sort_vs_realloc.cpp -- 验证 noexcept 对 std::sort 和 vector 扩容的影响
2+
// Standard: C++17
3+
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
4+
//
5+
// 核心结论:
6+
// - std::sort 只用移动,不区分移动操作是否 noexcept(两种类型都是 拷贝=0)
7+
// - vector 扩容通过 move_if_noexcept 选择策略:noexcept 类型用移动,
8+
// 非 noexcept 类型退回拷贝(强异常安全)
9+
#include <algorithm>
10+
#include <iostream>
11+
#include <string>
12+
#include <vector>
13+
14+
// 移动操作带 noexcept 的类型
15+
struct NoexceptType {
16+
std::string payload;
17+
int value;
18+
19+
static int copy_count;
20+
static int move_count;
21+
22+
NoexceptType(int v) : payload("data"), value(v) {}
23+
NoexceptType(const NoexceptType& o) : payload(o.payload + "_c"), value(o.value) {
24+
++copy_count;
25+
}
26+
NoexceptType(NoexceptType&& o) noexcept : payload(std::move(o.payload)), value(o.value) {
27+
o.payload = "(moved)";
28+
++move_count;
29+
}
30+
NoexceptType& operator=(NoexceptType&& o) noexcept {
31+
payload = std::move(o.payload);
32+
value = o.value;
33+
o.payload = "(moved)";
34+
++move_count;
35+
return *this;
36+
}
37+
NoexceptType& operator=(const NoexceptType& o) {
38+
payload = o.payload + "_c";
39+
value = o.value;
40+
++copy_count;
41+
return *this;
42+
}
43+
bool operator<(const NoexceptType& rhs) const { return value < rhs.value; }
44+
static void reset() {
45+
copy_count = 0;
46+
move_count = 0;
47+
}
48+
};
49+
50+
// ThrowingType 与 NoexceptType 完全相同,唯一区别是移动操作没有 noexcept
51+
struct ThrowingType {
52+
std::string payload;
53+
int value;
54+
55+
static int copy_count;
56+
static int move_count;
57+
58+
ThrowingType(int v) : payload("data"), value(v) {}
59+
ThrowingType(const ThrowingType& o) : payload(o.payload + "_c"), value(o.value) {
60+
++copy_count;
61+
}
62+
ThrowingType(ThrowingType&& o) // 注意:没有 noexcept
63+
: payload(std::move(o.payload)), value(o.value) {
64+
o.payload = "(moved)";
65+
++move_count;
66+
}
67+
ThrowingType& operator=(ThrowingType&& o) // 注意:没有 noexcept
68+
{
69+
payload = std::move(o.payload);
70+
value = o.value;
71+
o.payload = "(moved)";
72+
++move_count;
73+
return *this;
74+
}
75+
ThrowingType& operator=(const ThrowingType& o) {
76+
payload = o.payload + "_c";
77+
value = o.value;
78+
++copy_count;
79+
return *this;
80+
}
81+
bool operator<(const ThrowingType& rhs) const { return value < rhs.value; }
82+
static void reset() {
83+
copy_count = 0;
84+
move_count = 0;
85+
}
86+
};
87+
88+
int NoexceptType::copy_count = 0;
89+
int NoexceptType::move_count = 0;
90+
int ThrowingType::copy_count = 0;
91+
int ThrowingType::move_count = 0;
92+
93+
int main() {
94+
const int kCount = 5000;
95+
96+
// Test 1: std::sort(noexcept 类型)
97+
{
98+
std::vector<NoexceptType> vec;
99+
vec.reserve(kCount);
100+
for (int i = 0; i < kCount; ++i)
101+
vec.emplace_back(kCount - i);
102+
NoexceptType::reset();
103+
std::sort(vec.begin(), vec.end());
104+
std::cout << "noexcept sort: 拷贝=" << NoexceptType::copy_count
105+
<< " 移动=" << NoexceptType::move_count << "\n";
106+
}
107+
108+
// Test 2: std::sort(非 noexcept 类型)
109+
{
110+
std::vector<ThrowingType> vec;
111+
vec.reserve(kCount);
112+
for (int i = 0; i < kCount; ++i)
113+
vec.emplace_back(kCount - i);
114+
ThrowingType::reset();
115+
std::sort(vec.begin(), vec.end());
116+
std::cout << "非noexcept sort: 拷贝=" << ThrowingType::copy_count
117+
<< " 移动=" << ThrowingType::move_count << "\n";
118+
}
119+
120+
std::cout << "\n";
121+
122+
// Test 3: vector 扩容(noexcept 类型,无 reserve)
123+
{
124+
NoexceptType::reset();
125+
std::vector<NoexceptType> vec;
126+
for (int i = 0; i < 200; ++i)
127+
vec.emplace_back(i);
128+
std::cout << "noexcept 扩容: 拷贝=" << NoexceptType::copy_count
129+
<< " 移动=" << NoexceptType::move_count << "\n";
130+
}
131+
132+
// Test 4: vector 扩容(非 noexcept 类型,无 reserve)
133+
// ThrowingType 的扩容会退回拷贝,因为 move_if_noexcept 不选中它的移动
134+
{
135+
ThrowingType::reset();
136+
std::vector<ThrowingType> vec;
137+
for (int i = 0; i < 200; ++i)
138+
vec.emplace_back(i);
139+
std::cout << "非noexcept扩容: 拷贝=" << ThrowingType::copy_count
140+
<< " 移动=" << ThrowingType::move_count << "\n";
141+
}
142+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// push_back_emplace.cpp -- push_back(拷贝/移动) vs emplace_back(原位构造) 对比
2+
// Standard: C++17
3+
// 对应文档:vol2-modern-features/ch00-move-semantics/05-move-in-practice.md
4+
//
5+
// 核心结论:
6+
// - push_back(lvalue) 触发拷贝构造
7+
// - push_back(std::move(rvalue)) 触发移动构造
8+
// - emplace_back(构造参数) 连移动都省了,直接原位构造
9+
#include <iostream>
10+
#include <string>
11+
#include <vector>
12+
13+
class Heavy {
14+
std::string name_;
15+
std::vector<int> data_;
16+
17+
public:
18+
explicit Heavy(std::string name, std::size_t n) : name_(std::move(name)), data_(n, 42) {
19+
std::cout << " [" << name_ << "] 构造,数据量: " << data_.size() << "\n";
20+
}
21+
22+
Heavy(const Heavy& other) : name_(other.name_ + "_copy"), data_(other.data_) {
23+
std::cout << " [" << name_ << "] 拷贝构造\n";
24+
}
25+
26+
Heavy(Heavy&& other) noexcept : name_(std::move(other.name_)), data_(std::move(other.data_)) {
27+
other.name_ = "(moved-from)";
28+
std::cout << " [" << name_ << "] 移动构造\n";
29+
}
30+
31+
~Heavy() { std::cout << " [" << name_ << "] 析构,数据量: " << data_.size() << "\n"; }
32+
33+
const std::string& name() const { return name_; }
34+
std::size_t data_size() const { return data_.size(); }
35+
};
36+
37+
int main() {
38+
std::vector<Heavy> items;
39+
items.reserve(4);
40+
41+
std::cout << "=== push_back 左值(拷贝)===\n";
42+
Heavy h1("Alpha", 10000);
43+
items.push_back(h1);
44+
45+
std::cout << "\n=== push_back 右值(移动)===\n";
46+
Heavy h2("Beta", 10000);
47+
items.push_back(std::move(h2));
48+
49+
std::cout << "\n=== emplace_back 原位构造 ===\n";
50+
items.emplace_back("Gamma", 10000);
51+
52+
std::cout << "\n=== 程序结束 ===\n";
53+
return 0;
54+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Concepts 的三个经典坑,用宏切换复现(默认编译干净,加宏触发对应坑的编译失败)
2+
// 对应文章:02-constraining-templates.md、03-requires-expressions.md
3+
//
4+
// 默认编译(干净,main 演示「用 concept 包装优雅判断负例」的解法):
5+
// g++ -Wall -Wextra -std=c++20 concept_pitfalls.cpp -o cp && ./cp
6+
// 复现三个坑(每个都会编译失败,对照文章看报错):
7+
// g++ -std=c++20 -DDEMO_AMBIGUITY concept_pitfalls.cpp # 坑一:两个互不蕴含的 concept 重载,Duck
8+
// 同时满足 -> 歧义 g++ -std=c++20 -DDEMO_NOSUBSUME concept_pitfalls.cpp # 坑二:C2=C1<T>
9+
// 规范化后原子约束与 C1 相同,不 subsume -> 歧义 g++ -std=c++20 -DDEMO_HARD_ERROR
10+
// concept_pitfalls.cpp # 坑三:对具体类型直接写 requires 表达式 -> 硬错误
11+
#include <iostream>
12+
#include <string>
13+
14+
// 坑一用:两个彼此独立的 concept,谁也不蕴含谁
15+
template <typename T>
16+
concept Swimmable = requires(T t) { t.swim(); };
17+
template <typename T>
18+
concept Flyable = requires(T t) { t.fly(); };
19+
void act(Swimmable auto) {}
20+
void act(Flyable auto) {}
21+
22+
// 坑二用:C2 只是 C1<T> 换名,没有额外原子约束
23+
template <typename T>
24+
concept C1 = requires(T t) { t.a(); };
25+
template <typename T>
26+
concept C2 = C1<T>;
27+
void g(C1 auto) {}
28+
void g(C2 auto) {}
29+
30+
// 坑三的解法:把 requires 表达式包进 concept,求值时 T 是模板参数 -> SFINAE 友好,失败返回 false
31+
// 而非硬错误
32+
template <typename T>
33+
concept HasNope = requires(T t) { t.nope(); };
34+
35+
struct Duck {
36+
void swim() {}
37+
void fly() {}
38+
};
39+
struct X {
40+
void a() {}
41+
};
42+
43+
int main() {
44+
std::cout << std::boolalpha;
45+
// 解法演示:concept 包装后,不存在的成员优雅返回 false
46+
std::cout << "HasNope<std::string>: " << HasNope<std::string> << "\n"; // false,不硬错误
47+
48+
#if DEMO_AMBIGUITY
49+
act(Duck{}); // Duck 同时满足 Swimmable 和 Flyable,两者互不蕴含 -> 编译器选不出 -> 歧义
50+
#elif DEMO_NOSUBSUME
51+
g(X{}); // X 同时满足 C1 和 C2,但 C2 规范化后原子约束= C1,无真包含 -> 歧义
52+
#elif DEMO_HARD_ERROR
53+
static_assert(!requires(std::string s) { s.nope(); }); // 对具体类型 string 直接写 -> 硬错误
54+
#endif
55+
return 0;
56+
}

0 commit comments

Comments
 (0)