diff --git a/README.md b/README.md index e76252cad..021b89c3c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ --- -![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 433/433 docs translated +![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 439/439 docs translated ## 这是什么项目 diff --git a/code/examples/vol3/01_container_selection.cpp b/code/examples/vol3/01_container_selection.cpp new file mode 100644 index 000000000..944f1bc95 --- /dev/null +++ b/code/examples/vol3/01_container_selection.cpp @@ -0,0 +1,42 @@ +// Standard: C++20 +// 容器选择:演示「按操作选容器」——按位置存 vs 按键查,呼应选择决策树 +#include +#include +#include +#include +#include + +int main() { + std::cout << "== 按位置存:顺序容器,关心在哪插删 ==\n"; + std::vector v; + for (int i = 0; i < 5; ++i) { + v.push_back(i); // 尾部摊还 O(1) + } + std::list lt; + for (int i = 0; i < 5; ++i) { + lt.push_front(i); // 头部 O(1) + } + std::cout << "vector 尾插 5 个(尾部摊还 O(1)),list 头插 5 个(头部 O(1))\n"; + std::cout << "→ 频繁头尾进出用 deque,主要尾部增长用 vector(务必 reserve)\n"; + + std::cout << "\n== 按键查:关联容器,关心按什么查 ==\n"; + constexpr int N = 100'000; + std::map om; + std::unordered_map um; + for (int i = 0; i < N; ++i) { + om[i] = i; + um[i] = i; + } + std::cout << "map.find(N/2) 命中: " << (om.find(N / 2) != om.end()) + << "(O(log n) 红黑树,可有序遍历)\n"; + std::cout << "unordered_map.find(N/2) 命中: " << (um.find(N / 2) != um.end()) + << "(平均 O(1) 哈希,最快)\n"; + std::cout << "→ 要有序遍历用 map,只要快查用 unordered(记得 reserve)\n"; + + std::cout << "\n== 决策三问(挑容器先问这三件事)==\n"; + std::cout << "1) 大小编译期已知且不变?→ array\n"; + std::cout << "2) 按键查找?→ 有序遍历用 map/set,否则 unordered(平均 O(1))\n"; + std::cout << "3) 按位置存?→ 头尾用 deque,尾部用 vector,已知位置频繁增删用 list\n"; + std::cout << "拿不准就 vector:连续、尾部摊还 O(1)、接口最全,覆盖面最广的安全牌\n"; + return 0; +} diff --git a/code/examples/vol34567/01_array.cpp b/code/examples/vol3/02_array.cpp similarity index 100% rename from code/examples/vol34567/01_array.cpp rename to code/examples/vol3/02_array.cpp diff --git a/code/examples/vol34567/15_vector_deep_dive.cpp b/code/examples/vol3/03_vector_deep_dive.cpp similarity index 100% rename from code/examples/vol34567/15_vector_deep_dive.cpp rename to code/examples/vol3/03_vector_deep_dive.cpp diff --git a/code/examples/vol34567/16_string_memory.cpp b/code/examples/vol3/04_string_memory.cpp similarity index 100% rename from code/examples/vol34567/16_string_memory.cpp rename to code/examples/vol3/04_string_memory.cpp diff --git a/code/examples/vol3/05_deque_list_forward_list.cpp b/code/examples/vol3/05_deque_list_forward_list.cpp new file mode 100644 index 000000000..fdf9075c5 --- /dev/null +++ b/code/examples/vol3/05_deque_list_forward_list.cpp @@ -0,0 +1,53 @@ +// Standard: C++20 +// deque / list / forward_list:vector 之外的三个选择——头插都 O(1),但内存布局与 splice 各异 +#include +#include +#include +#include + +int main() { + std::cout << "== 三者头插都 O(1),但存储布局不同 ==\n"; + constexpr int N = 100'000; + + std::deque dq; + for (int i = 0; i < N; ++i) { + dq.push_front(i); // 分段连续,头尾均 O(1) + } + + std::list lt; + for (int i = 0; i < N; ++i) { + lt.push_front(i); // 双向链表节点 + } + + std::forward_list fl; + for (int i = 0; i < N; ++i) { + fl.push_front(i); // 单向链表,最省内存(无 prev 指针) + } + std::cout << "各头插 " << N << " 个,复杂度都是 O(1)\n"; + + std::cout << "\n== sizeof:forward_list 最省,deque 有分段控制开销 ==\n"; + std::cout << "sizeof(deque) = " << sizeof(dq) << '\n'; + std::cout << "sizeof(list) = " << sizeof(lt) << '\n'; + std::cout << "sizeof(forward_list) = " << sizeof(fl) << '\n'; + + std::cout << "\n== list::splice:O(1) 把整串节点搬过来,零拷贝 ==\n"; + std::list a{1, 2, 3}; + std::list b{10, 20}; + auto it = a.begin(); + ++it; // 指向元素 2 + a.splice(it, b); // 把 b 整个接到 a 的 it 之前,搬节点不拷贝 + std::cout << "splice 后 a = "; + for (auto x : a) { + std::cout << x << ' '; + } + std::cout << "\nb 现在 size = " << b.size() << "(节点被搬走,b 空了)\n"; + + std::cout << "\n== forward_list 没有 size():单向链表数元素要 O(n) ==\n"; + int cnt = 0; + for (auto x : fl) { + (void)x; + ++cnt; + } + std::cout << "forward_list 手动数 = " << cnt << '\n'; + return 0; +} diff --git a/code/examples/vol3/06_map_set.cpp b/code/examples/vol3/06_map_set.cpp new file mode 100644 index 000000000..b01971ce4 --- /dev/null +++ b/code/examples/vol3/06_map_set.cpp @@ -0,0 +1,43 @@ +// Standard: C++20 +// map / set:底层红黑树按键有序、异构查找(transparent 比较器,string_view 直接查)、extract +// 节点搬家 +#include +#include +#include +#include + +int main() { + std::cout << "== map 底层红黑树:按键自动有序 ==\n"; + std::map m; + m[3] = "three"; + m[1] = "one"; + m[2] = "two"; + std::cout << "插入 3,1,2 后遍历(自动有序):"; + for (const auto& [k, v] : m) { + std::cout << k << ':' << v << ' '; + } + std::cout << '\n'; + + std::cout << "\n== 异构查找:用 string_view 查 string 的 map,免构造临时 string ==\n"; + std::map> sm; // std::less<> = 透明比较器 + sm["apple"] = 1; + sm["banana"] = 2; + std::string_view key = "banana"; + auto it = sm.find(key); // string_view 直接查,不构造临时 string + if (it != sm.end()) { + std::cout << "find(string_view) 命中: " << it->second << '\n'; + } + std::cout << "(用 std::less<> 而非 std::less,比较器才支持异构 key)\n"; + + std::cout << "\n== extract:把节点从一棵 map 搬到另一棵,零拷贝 ==\n"; + std::map a{{1, "one"}, {2, "two"}}; + std::map b; + auto node = a.extract(1); // 抽出节点(连带 string,不拷贝) + b.insert(std::move(node)); // 接到 b + std::cout << "extract(1) 后 a.size = " << a.size() << ", b.size = " << b.size() << '\n'; + std::cout << "(extract 搬节点不拷贝 string,适合改 key、换分配器、跨 map 转移)\n"; + + std::cout << "\n== 复杂度:查找/插入/删除均 O(log n) ==\n"; + std::cout << "要有序遍历用 map/set;只要平均 O(1) 查找用 unordered 版\n"; + return 0; +} diff --git a/code/examples/vol3/07_unordered_map_set.cpp b/code/examples/vol3/07_unordered_map_set.cpp new file mode 100644 index 000000000..103bfd2f9 --- /dev/null +++ b/code/examples/vol3/07_unordered_map_set.cpp @@ -0,0 +1,41 @@ +// Standard: C++20 +// unordered_map:哈希桶、rehash 触发的质数桶序列、load_factor、reserve 预撑桶 +#include +#include + +int main() { + std::cout << "== rehash 触发质数桶序列(bucket_count 跳变)==\n"; + std::unordered_map m; + std::size_t last = m.bucket_count(); + std::cout << "初始 bucket_count = " << last << '\n'; + for (int i = 0; i < 200; ++i) { + m[i] = i; + if (m.bucket_count() != last) { + std::cout << "插入 " << i << " 后 rehash: " << last << " -> " << m.bucket_count() + << "(load_factor=" << m.load_factor() << ")\n"; + last = m.bucket_count(); + } + } + + std::cout << "\n== 桶分布:看元素怎么落桶(链地址法)==\n"; + std::unordered_map small; + for (int i = 0; i < 10; ++i) { + small[i] = i; + } + int non_empty = 0; + for (std::size_t b = 0; b < small.bucket_count(); ++b) { + if (small.bucket_size(b) > 0) { + ++non_empty; + std::cout << "bucket " << b << " 有 " << small.bucket_size(b) << " 个元素\n"; + } + } + std::cout << "共 " << small.bucket_count() << " 个桶," << non_empty << " 个非空\n"; + std::cout << "max_load_factor = " << small.max_load_factor() + << "(默认 1.0,平均每桶元素数超了就 rehash)\n"; + + std::cout << "\n== reserve 预撑桶,避免 hot path 里反复 rehash ==\n"; + std::unordered_map reserved; + reserved.reserve(1000); // 内部按 max_load_factor 算出足够的桶 + std::cout << "reserve(1000) 后 bucket_count = " << reserved.bucket_count() << '\n'; + return 0; +} diff --git a/code/examples/vol34567/02_span.cpp b/code/examples/vol3/08_span.cpp similarity index 100% rename from code/examples/vol34567/02_span.cpp rename to code/examples/vol3/08_span.cpp diff --git a/code/examples/vol3/09_container_adapters.cpp b/code/examples/vol3/09_container_adapters.cpp new file mode 100644 index 000000000..c2f097650 --- /dev/null +++ b/code/examples/vol3/09_container_adapters.cpp @@ -0,0 +1,52 @@ +// Standard: C++20 +// 容器适配器:stack(LIFO) / queue(FIFO) / priority_queue(堆)——priority_queue 默认最大堆,greater +// 变最小堆 +#include +#include +#include +#include +#include + +int main() { + std::cout << "== stack:LIFO,top/push/pop 全在 back 一端 ==\n"; + std::stack s; + for (int x : {1, 2, 3}) { + s.push(x); + } + std::cout << "push 1,2,3 后,top = " << s.top() << "(最后进的先出)\n"; + + std::cout << "\n== queue:FIFO,push 在 back、front/pop 在 front ==\n"; + std::queue q; + for (int x : {1, 2, 3}) { + q.push(x); + } + std::cout << "push 1,2,3 后,front = " << q.front() << " back = " << q.back() << '\n'; + + std::cout << "\n== priority_queue 默认最大堆(vector + less)==\n"; + std::priority_queue pq; + for (int x : {5, 1, 9, 3, 7}) { + pq.push(x); + } + std::cout << "依次 pop: "; + while (!pq.empty()) { + std::cout << pq.top() << ' '; + pq.pop(); + } + std::cout << '\n'; + + std::cout << "\n== 换 greater 变最小堆 ==\n"; + std::priority_queue, std::greater> min_pq; + for (int x : {5, 1, 9, 3, 7}) { + min_pq.push(x); + } + std::cout << "依次 pop: "; + while (!min_pq.empty()) { + std::cout << min_pq.top() << ' '; + min_pq.pop(); + } + std::cout << '\n'; + + std::cout << "\n== 复杂度:top O(1),push/pop O(log n) ==\n"; + std::cout << "(push = push_back + push_heap;pop = pop_heap + pop_back;底层就是堆算法)\n"; + return 0; +} diff --git a/code/examples/vol3/10_new_containers.cpp b/code/examples/vol3/10_new_containers.cpp new file mode 100644 index 000000000..45a8f40a2 --- /dev/null +++ b/code/examples/vol3/10_new_containers.cpp @@ -0,0 +1,37 @@ +// Standard: C++26 +// 新标准容器:flat_map(C++23 拍平排序 vector) / inplace_vector(C++26 定容不堆分配) / mdspan(C++23 +// 多维视图) +#include +#include +#include +#include +#include + +int main() { + std::printf("== flat_map(C++23):底层排序 vector,find O(log n) 且 cache 友好 ==\n"); + std::flat_map fm; + fm.insert({3, "three"}); + fm.insert({1, "one"}); + fm.insert({2, "two"}); // O(n):维护有序要搬移 + auto it = fm.find(2); // O(log n):二分查找 + std::printf("find(2) = %s\n", it->second.c_str()); + std::printf("有序遍历:"); + for (auto [k, v] : fm) { + std::printf("%d:%s ", k, v.c_str()); + } + std::printf("\n"); + + std::printf("\n== inplace_vector(C++26):容量编译期定死 N,元素存对象内,绝不堆分配 ==\n"); + std::inplace_vector ipv; + for (int i = 1; i <= 5; ++i) { + ipv.push_back(i); + } + std::printf("size = %zu, capacity = %zu(无 new,放栈/静态区)\n", ipv.size(), ipv.capacity()); + + std::printf("\n== mdspan(C++23):一维内存的多维视图,m[i,j] 多维下标(P2128)==\n"); + int raw[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + // 把 12 个 int 当 3 行 4 列(行优先) + std::mdspan> m(raw); + std::printf("m[1,2] = %d m[2,3] = %d rank = %zu\n", m[1, 2], m[2, 3], m.rank()); + return 0; +} diff --git a/code/examples/vol3/11_initializer_lists.cpp b/code/examples/vol3/11_initializer_lists.cpp new file mode 100644 index 000000000..ed744b13f --- /dev/null +++ b/code/examples/vol3/11_initializer_lists.cpp @@ -0,0 +1,44 @@ +// Standard: C++20 +// std::initializer_list:花括号背后的只读视图,以及元素无法 move 的「移动陷阱」 +#include +#include +#include + +class Tracked { + public: + int id; + static int copy_count; + static int move_count; + + explicit Tracked(int i) : id(i) {} + Tracked(const Tracked& o) : id(o.id) { ++copy_count; } + Tracked(Tracked&& o) noexcept : id(o.id) { ++move_count; } +}; +int Tracked::copy_count = 0; +int Tracked::move_count = 0; + +int main() { + std::cout << "== initializer_list:编译器为 {…} 生成的只读视图 ==\n"; + std::initializer_list il = {1, 2, 3, 4}; + std::cout << "size = " << il.size() << '\n'; + int sum = 0; + for (auto x : il) { + sum += x; + } + std::cout << "sum = " << sum << '\n'; + + std::cout << "\n== 移动陷阱:initializer_list 元素是 const,进容器只能拷贝 ==\n"; + Tracked::copy_count = 0; + Tracked::move_count = 0; + std::vector v{Tracked(1), Tracked(2), Tracked(3)}; + std::cout << "vector{3 个 Tracked}: copies = " << Tracked::copy_count + << ", moves = " << Tracked::move_count << '\n'; + std::cout << "(元素是 const → 无法 move 出 initializer_list,只能拷贝进 vector)\n"; + + std::cout << "\n== 花括号初始化的重载优先级 ==\n"; + std::vector a{1, 2, 3}; // 走 initializer_list 构造:3 个元素 [1,2,3] + std::vector b(3, 1); // 走 (count, value):3 个 1 + std::cout << "a{1,2,3} = [" << a[0] << ',' << a[1] << ',' << a[2] << "] (initializer_list)\n"; + std::cout << "b(3,1) = [" << b[0] << ',' << b[1] << ',' << b[2] << "] (count, value)\n"; + return 0; +} diff --git a/code/examples/vol34567/04_object_size.cpp b/code/examples/vol3/12_object_size.cpp similarity index 100% rename from code/examples/vol34567/04_object_size.cpp rename to code/examples/vol3/12_object_size.cpp diff --git a/code/examples/vol3/13_custom_allocators.cpp b/code/examples/vol3/13_custom_allocators.cpp new file mode 100644 index 000000000..8c4074c25 --- /dev/null +++ b/code/examples/vol3/13_custom_allocators.cpp @@ -0,0 +1,50 @@ +// Standard: C++20 +// 自定义分配器:手写一个 bump(线性)分配器原型 + std::pmr 让容器在栈 buffer 里分配 +#include +#include +#include +#include + +// 最简 bump arena:指针线性前进,不回收单块,整块 reset(典型的帧/临时分配场景) +template class Arena { + alignas(std::max_align_t) std::byte buf_[N]; + std::size_t offset_ = 0; + + public: + void* alloc(std::size_t bytes, std::size_t align) { + std::size_t space = N - offset_; + void* ptr = buf_ + offset_; + if (std::align(align, bytes, ptr, space)) { + offset_ = static_cast(ptr) - buf_ + bytes; + return ptr; + } + return nullptr; // 满了 + } + std::size_t used() const { return offset_; } + static constexpr std::size_t capacity() { return N; } +}; + +int main() { + std::cout << "== 手写 bump arena:指针线性前进,分配 O(1) ==\n"; + Arena<256> arena; + void* p1 = arena.alloc(16, alignof(int)); + void* p2 = arena.alloc(32, alignof(double)); + void* p3 = arena.alloc(64, alignof(std::max_align_t)); + std::cout << "alloc 16/32/64 字节后,used = " << arena.used() << " / " << arena.capacity() + << '\n'; + std::cout << "(连续地址:p1=" << p1 << " p2=" << p2 << " p3=" << p3 << ")\n"; + + std::cout << "\n== std::pmr:让 vector 在栈上的一块 buffer 里分配,零堆分配 ==\n"; + std::byte stack_buf[1024]; + std::pmr::monotonic_buffer_resource mbr(stack_buf, sizeof(stack_buf)); + { + std::pmr::vector v(&mbr); // 这 vector 的 new/delete 走 mbr,即栈 buffer + for (int i = 0; i < 200; ++i) { + v.push_back(i); + } + std::cout << "pmr::vector 存了 " << v.size() + << " 个 int,分配全部落在栈上 1024 字节 buffer,没碰堆\n"; + } + std::cout << "(monotonic_buffer_resource 就是 bump 思路:只进不退,整块释放)\n"; + return 0; +} diff --git a/code/examples/vol34567/17_char8_t.cpp b/code/examples/vol3/14_char8_t.cpp similarity index 100% rename from code/examples/vol34567/17_char8_t.cpp rename to code/examples/vol3/14_char8_t.cpp diff --git a/code/examples/vol34567/03_initializer_lists.cpp b/code/examples/vol34567/03_initializer_lists.cpp deleted file mode 100644 index 9f04d7b01..000000000 --- a/code/examples/vol34567/03_initializer_lists.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Constructor optimization: initializer list vs assignment -#include -#include - -// ---- BAD: assignment in constructor body ---- -class TimerBad { - public: - TimerBad(uint32_t period) { - period_ = period; // default-init THEN assign - enabled_ = false; - } - uint32_t period_; - bool enabled_; -}; - -// ---- GOOD: initializer list ---- -class TimerGood { - public: - TimerGood(uint32_t period) - : period_(period) // direct initialization - , - enabled_(false) {} - uint32_t period_; - bool enabled_; -}; - -// ---- Const member: ONLY works with init list ---- -class Device { - public: - Device(uint32_t id) : id_(id) {} - const uint32_t id_; -}; - -// ---- Reference member: ONLY works with init list ---- -struct GPIO {}; -class Driver { - public: - Driver(GPIO& gpio) : gpio_(gpio) {} - GPIO& gpio_; -}; - -// ---- No default constructor: MUST use init list ---- -class SpiBus { - public: - explicit SpiBus(uint32_t base_addr) : base_addr_(base_addr) {} - uint32_t base_addr_; -}; - -class Sensor { - public: - Sensor() : spi_(SPI1_BASE) {} - - private: - static constexpr uint32_t SPI1_BASE = 0x40013000; - SpiBus spi_; -}; - -int main() { - TimerBad tb(1000); - TimerGood tg(1000); - - std::cout << "Bad timer: period=" << tb.period_ << " enabled=" << tb.enabled_ << '\n'; - std::cout << "Good timer: period=" << tg.period_ << " enabled=" << tg.enabled_ << '\n'; - - // sizeof should be identical - static_assert(sizeof(TimerBad) == sizeof(TimerGood)); - std::cout << "sizeof(Timer) = " << sizeof(TimerGood) << '\n'; - - Device dev(42); - std::cout << "Device id = " << dev.id_ << '\n'; - - GPIO gpio; - Driver drv(gpio); - std::cout << "Driver holds GPIO reference at " << static_cast(&drv.gpio_) << '\n'; - - Sensor s; - std::cout << "Sensor constructed with SpiBus\n"; - - return 0; -} diff --git a/code/examples/vol34567/05_if_constexpr.cpp b/code/examples/vol4/05_if_constexpr.cpp similarity index 100% rename from code/examples/vol34567/05_if_constexpr.cpp rename to code/examples/vol4/05_if_constexpr.cpp diff --git a/code/examples/vol34567/06_crtp.cpp b/code/examples/vol4/06_crtp.cpp similarity index 100% rename from code/examples/vol34567/06_crtp.cpp rename to code/examples/vol4/06_crtp.cpp diff --git a/code/examples/vol34567/07_barton_nackman.cpp b/code/examples/vol4/07_barton_nackman.cpp similarity index 100% rename from code/examples/vol34567/07_barton_nackman.cpp rename to code/examples/vol4/07_barton_nackman.cpp diff --git a/code/examples/vol34567/08_spaceship.cpp b/code/examples/vol4/08_spaceship.cpp similarity index 100% rename from code/examples/vol34567/08_spaceship.cpp rename to code/examples/vol4/08_spaceship.cpp diff --git a/code/examples/vol34567/09_std_thread.cpp b/code/examples/vol5/09_std_thread.cpp similarity index 100% rename from code/examples/vol34567/09_std_thread.cpp rename to code/examples/vol5/09_std_thread.cpp diff --git a/code/examples/vol34567/10_mutex_raii.cpp b/code/examples/vol5/10_mutex_raii.cpp similarity index 100% rename from code/examples/vol34567/10_mutex_raii.cpp rename to code/examples/vol5/10_mutex_raii.cpp diff --git a/code/examples/vol34567/11_atomic.cpp b/code/examples/vol5/11_atomic.cpp similarity index 100% rename from code/examples/vol34567/11_atomic.cpp rename to code/examples/vol5/11_atomic.cpp diff --git a/code/examples/vol34567/12_inline_optimization.cpp b/code/examples/vol6/12_inline_optimization.cpp similarity index 100% rename from code/examples/vol34567/12_inline_optimization.cpp rename to code/examples/vol6/12_inline_optimization.cpp diff --git a/code/examples/vol34567/13_perf_eval.cpp b/code/examples/vol6/13_perf_eval.cpp similarity index 100% rename from code/examples/vol34567/13_perf_eval.cpp rename to code/examples/vol6/13_perf_eval.cpp diff --git a/code/examples/vol34567/14_compiler_options.cpp b/code/examples/vol7/14_compiler_options.cpp similarity index 100% rename from code/examples/vol34567/14_compiler_options.cpp rename to code/examples/vol7/14_compiler_options.cpp diff --git a/documents/404.md b/documents/404.md index 4e9ed0a64..ff4fbce78 100644 --- a/documents/404.md +++ b/documents/404.md @@ -1,13 +1,13 @@ --- -layout: page -title: "页面未找到" -description: "404 页面未找到" -tags: - - host chapter: 0 +description: 404 页面未找到 +layout: page order: 0 +reading_time_minutes: 1 +tags: +- host +title: 页面未找到 --- - # 404 **页面未找到** diff --git a/documents/appendix/terminology.md b/documents/appendix/terminology.md index be5745bba..cfda08caf 100644 --- a/documents/appendix/terminology.md +++ b/documents/appendix/terminology.md @@ -1,10 +1,11 @@ --- -title: 术语表 -description: 项目中英文技术术语标准翻译对照表 -tags: - - 基础 chapter: 99 +description: 项目中英文技术术语标准翻译对照表 order: 0 +reading_time_minutes: 8 +tags: +- 基础 +title: 术语表 --- # 术语表 diff --git a/documents/community/dev/01-iteration-cadence.md b/documents/community/dev/01-iteration-cadence.md index 76ebba739..c0ceeb5ea 100644 --- a/documents/community/dev/01-iteration-cadence.md +++ b/documents/community/dev/01-iteration-cadence.md @@ -1,11 +1,12 @@ --- -title: "网站迭代节奏" -description: "Tutorial_AwesomeModernCPP 的内容产出、站点维护、PR/Issue 处理和发版节奏" chapter: 1 +description: Tutorial_AwesomeModernCPP 的内容产出、站点维护、PR/Issue 处理和发版节奏 order: 1 -tags: ["工程实践"] +reading_time_minutes: 4 +tags: +- 工程实践 +title: 网站迭代节奏 --- - # 网站迭代节奏 Tutorial_AwesomeModernCPP 的迭代以内容产出为主,版本号用于度量内容推进幅度。站点维护、PR 和 Issue 处理服务于主线内容,不反过来接管主线节奏。 diff --git a/documents/compilation/01-compilation-and-linking-overview.md b/documents/compilation/01-compilation-and-linking-overview.md index afab04fa5..cb6df0368 100644 --- a/documents/compilation/01-compilation-and-linking-overview.md +++ b/documents/compilation/01-compilation-and-linking-overview.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++的编译与链接技术:导论 -description: '' +chapter: 13 +difficulty: intermediate +order: 1 +platform: host +reading_time_minutes: 32 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 1 +title: 深入理解C/C++的编译与链接技术:导论 +description: '' --- # 深入理解C/C++的编译与链接技术:导论 diff --git a/documents/compilation/02-reuse-concept.md b/documents/compilation/02-reuse-concept.md index 889bd030e..689577ce2 100644 --- a/documents/compilation/02-reuse-concept.md +++ b/documents/compilation/02-reuse-concept.md @@ -1,14 +1,15 @@ --- -title: 深入理解CC++的编译与链接技术2:动态库静态库导论 -description: '' +chapter: 13 +difficulty: intermediate +order: 2 +platform: host +reading_time_minutes: 12 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 2 +title: 深入理解CC++的编译与链接技术2:动态库静态库导论 +description: '' --- # 深入理解CC++的编译与链接技术2:动态库静态库导论 diff --git a/documents/compilation/03-creating-and-using-static-libs.md b/documents/compilation/03-creating-and-using-static-libs.md index 74540c0a0..abb912487 100644 --- a/documents/compilation/03-creating-and-using-static-libs.md +++ b/documents/compilation/03-creating-and-using-static-libs.md @@ -1,14 +1,15 @@ --- -title: 深入理解CC++的编译与链接技术3:如何制作和使用静态库 -description: '' +chapter: 13 +difficulty: intermediate +order: 3 +platform: host +reading_time_minutes: 6 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 3 +title: 深入理解CC++的编译与链接技术3:如何制作和使用静态库 +description: '' --- # 深入理解CC++的编译与链接技术3:如何制作和使用静态库 diff --git a/documents/compilation/04-dynamic-libraries-1.md b/documents/compilation/04-dynamic-libraries-1.md index 6a27efe5f..5105d5d27 100644 --- a/documents/compilation/04-dynamic-libraries-1.md +++ b/documents/compilation/04-dynamic-libraries-1.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++编译与链接技术4:动态库A1:基本讨论之`-fPIC` -description: '' +chapter: 13 +difficulty: intermediate +order: 4 +platform: host +reading_time_minutes: 4 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 4 +title: 深入理解C/C++编译与链接技术4:动态库A1:基本讨论之`-fPIC` +description: '' --- # 深入理解C/C++编译与链接技术4:动态库A1:基本讨论之`-fPIC` diff --git a/documents/compilation/05-dynamic-library-design.md b/documents/compilation/05-dynamic-library-design.md index 3d1dcf700..c3e19312e 100644 --- a/documents/compilation/05-dynamic-library-design.md +++ b/documents/compilation/05-dynamic-library-design.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++的编译链接技术6——A2:动态库设计基础之ABI设计接口 -description: '' +chapter: 13 +difficulty: intermediate +order: 5 +platform: host +reading_time_minutes: 11 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 5 +title: 深入理解C/C++的编译链接技术6——A2:动态库设计基础之ABI设计接口 +description: '' --- # 深入理解C/C++的编译链接技术6——A2:动态库设计基础之ABI设计接口 diff --git a/documents/compilation/06-symbol-visibility.md b/documents/compilation/06-symbol-visibility.md index c3e28a7f3..be4450622 100644 --- a/documents/compilation/06-symbol-visibility.md +++ b/documents/compilation/06-symbol-visibility.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++编译技术——动态库A3:聊一聊符号可见性 -description: '' +chapter: 13 +difficulty: intermediate +order: 6 +platform: host +reading_time_minutes: 4 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 6 +title: 深入理解C/C++编译技术——动态库A3:聊一聊符号可见性 +description: '' --- # 深入理解C/C++编译技术——动态库A3:聊一聊符号可见性 diff --git a/documents/compilation/07-symbol-missing-and-runtime-loading.md b/documents/compilation/07-symbol-missing-and-runtime-loading.md index 98f281a5e..30cae5d25 100644 --- a/documents/compilation/07-symbol-missing-and-runtime-loading.md +++ b/documents/compilation/07-symbol-missing-and-runtime-loading.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++编译技术——动态库A4:链接时符号缺失行为与运行时动态加载 -description: '' +chapter: 13 +difficulty: intermediate +order: 7 +platform: host +reading_time_minutes: 7 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 7 +title: 深入理解C/C++编译技术——动态库A4:链接时符号缺失行为与运行时动态加载 +description: '' --- # 深入理解C/C++编译技术——动态库A4:链接时符号缺失行为与运行时动态加载 diff --git a/documents/compilation/08-library-search-logic.md b/documents/compilation/08-library-search-logic.md index 5b0585ed0..166ccb6ee 100644 --- a/documents/compilation/08-library-search-logic.md +++ b/documents/compilation/08-library-search-logic.md @@ -1,14 +1,15 @@ --- -title: 深入理解C/C++的编译与链接技术8:库文件检索逻辑 -description: '' +chapter: 13 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 8 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 8 +title: 深入理解C/C++的编译与链接技术8:库文件检索逻辑 +description: '' --- # 深入理解C/C++的编译与链接技术8:库文件检索逻辑 diff --git a/documents/compilation/09-dynamic-library-details.md b/documents/compilation/09-dynamic-library-details.md index 3add74931..7b18edc73 100644 --- a/documents/compilation/09-dynamic-library-details.md +++ b/documents/compilation/09-dynamic-library-details.md @@ -1,14 +1,15 @@ --- -title: 深入理解CC++的编译与链接技术9:动态库细节(完结) -description: '' +chapter: 13 +difficulty: intermediate +order: 9 +platform: host +reading_time_minutes: 13 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 9 +title: 深入理解CC++的编译与链接技术9:动态库细节(完结) +description: '' --- # 深入理解CC++的编译与链接技术9:动态库细节(完结) diff --git a/documents/compilation/10-dynamic-lib-as-executable.md b/documents/compilation/10-dynamic-lib-as-executable.md index 89d61b58f..f6ad6657f 100644 --- a/documents/compilation/10-dynamic-lib-as-executable.md +++ b/documents/compilation/10-dynamic-lib-as-executable.md @@ -1,14 +1,15 @@ --- -title: 深入理解CC++的编译与链接技术(番外):动态库可以像可执行文件那样执行嘛? -description: '' +chapter: 13 +difficulty: intermediate +order: 10 +platform: host +reading_time_minutes: 10 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 13 -order: 10 +title: 深入理解CC++的编译与链接技术(番外):动态库可以像可执行文件那样执行嘛? +description: '' --- # 深入理解CC++的编译与链接技术(番外):动态库可以像可执行文件那样执行嘛? diff --git a/documents/cpp-reference/concurrency/01-atomic.md b/documents/cpp-reference/concurrency/01-atomic.md index 6c5ffce20..08639c3e8 100644 --- a/documents/cpp-reference/concurrency/01-atomic.md +++ b/documents/cpp-reference/concurrency/01-atomic.md @@ -1,16 +1,21 @@ --- -title: "std::atomic" -description: "无锁原子操作类型,用于多线程间无数据竞争的安全数据共享" chapter: 99 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 无锁原子操作类型,用于多线程间无数据竞争的安全数据共享 +difficulty: intermediate order: 1 +reading_time_minutes: 2 tags: - - host - - atomic - - intermediate -difficulty: intermediate -cpp_standard: [11, 14, 17, 20, 23] +- host +- atomic +- intermediate +title: std::atomic --- - # std::atomic(C++11) ## 一句话 diff --git a/documents/cpp-reference/concurrency/02-thread.md b/documents/cpp-reference/concurrency/02-thread.md index 2ed958a74..a0463bfb7 100644 --- a/documents/cpp-reference/concurrency/02-thread.md +++ b/documents/cpp-reference/concurrency/02-thread.md @@ -1,16 +1,21 @@ --- -title: "std::thread" -description: "表示单个执行线程的类,允许并发执行多个函数" chapter: 99 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 表示单个执行线程的类,允许并发执行多个函数 +difficulty: beginner order: 2 +reading_time_minutes: 2 tags: - - host - - mutex - - beginner -difficulty: beginner -cpp_standard: [11, 14, 17, 20, 23] +- host +- mutex +- beginner +title: std::thread --- - # std::thread(C++11) ## 一句话 diff --git a/documents/cpp-reference/concurrency/03-mutex.md b/documents/cpp-reference/concurrency/03-mutex.md index 6a366570f..c049a0d26 100644 --- a/documents/cpp-reference/concurrency/03-mutex.md +++ b/documents/cpp-reference/concurrency/03-mutex.md @@ -1,16 +1,21 @@ --- -title: "std::mutex" -description: "提供独占、非递归的所有权语义,用于保护共享数据免受多线程同时访问。" chapter: 99 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 提供独占、非递归的所有权语义,用于保护共享数据免受多线程同时访问。 +difficulty: beginner order: 3 +reading_time_minutes: 1 tags: - - host - - mutex - - beginner -difficulty: beginner -cpp_standard: [11, 14, 17, 20, 23] +- host +- mutex +- beginner +title: std::mutex --- - # std::mutex(C++11) ## 一句话 diff --git a/documents/cpp-reference/concurrency/04-jthread.md b/documents/cpp-reference/concurrency/04-jthread.md index c2817b100..7497e747d 100644 --- a/documents/cpp-reference/concurrency/04-jthread.md +++ b/documents/cpp-reference/concurrency/04-jthread.md @@ -1,16 +1,18 @@ --- -title: "std::jthread" -description: "自动 join 的线程类,析构时发送停止请求并等待线程退出" chapter: 99 +cpp_standard: +- 20 +- 23 +description: 自动 join 的线程类,析构时发送停止请求并等待线程退出 +difficulty: beginner order: 4 +reading_time_minutes: 2 tags: - - host - - cpp-modern - - beginner -difficulty: beginner -cpp_standard: [20, 23] +- host +- cpp-modern +- beginner +title: std::jthread --- - # std::initializer_list (C++11) ## In a Nutshell -A lightweight, read-only proxy object that allows us to pass an arbitrary number of same-type initial values to containers or custom classes using braces `{}`. +A lightweight, read-only proxy object that allows you to conveniently pass an arbitrary number of initial values of the same type to containers or custom classes using brace initialization `{}`. ## Header @@ -49,11 +50,11 @@ A lightweight, read-only proxy object that allows us to pass an arbitrary number | Operation | Signature | Description | |-----------|-----------|-------------| | Constructor | `initializer_list() noexcept` | Creates an empty list (usually implicitly constructed by the compiler) | -| Element count | `std::size_t size() const noexcept` | Returns the number of elements in the list | -| Begin pointer | `const T* begin() const noexcept` | Pointer to the first element | -| End pointer | `const T* end() const noexcept` | Pointer to one past the last element | -| Begin iterator | `const T* begin(std::initializer_list il) noexcept` | Overloaded `std::begin` | -| End iterator | `const T* end(std::initializer_list il) noexcept` | Overloaded `std::end` | +| Element Count | `std::size_t size() const noexcept` | Returns the number of elements in the list | +| Begin Pointer | `const T* begin() const noexcept` | Pointer to the first element | +| End Pointer | `const T* end() const noexcept` | Pointer to one past the last element | +| Begin Iterator | `const T* begin(std::initializer_list il) noexcept` | Overloaded `std::begin` | +| End Iterator | `const T* end(std::initializer_list il) noexcept` | Overloaded `std::end` | ## Minimal Example @@ -80,9 +81,9 @@ int main() { ## Embedded Applicability: High -- The underlying implementation typically contains only a pointer and a length (or two pointers), resulting in minimal memory overhead -- Copying `std::initializer_list` does not copy the underlying array; it only copies the proxy object itself, incurring no additional allocation overhead -- The underlying array may reside in read-only memory, making it suitable for initializing ROM-able static configuration tables +- The underlying implementation typically contains only a pointer and a length (or two pointers), resulting in minimal memory overhead. +- Copying `std::initializer_list` does not copy the underlying array; it only copies the proxy object itself, incurring no additional allocation overhead. +- The underlying array may be stored in read-only memory, making it suitable for initializing static configuration tables placed in ROM. ## Compiler Support @@ -92,9 +93,9 @@ int main() { ## See Also -- [Tutorial: Initializer Lists](../../vol3-standard-library/01-initializer-lists.md) +- [Tutorial: Initializer Lists](../../vol3-standard-library/11-initializer-lists.md) - [cppreference: std::initializer_list](https://en.cppreference.com/w/cpp/utility/initializer_list) --- -*Some content referenced from [cppreference.com](https://en.cppreference.com/), licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)* +*Portions of content adapted from [cppreference.com](https://en.cppreference.com/), available under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license* diff --git a/documents/en/cpp-reference/containers/06-filesystem.md b/documents/en/cpp-reference/containers/06-filesystem.md index 2ef91fa2d..2a137d5b8 100644 --- a/documents/en/cpp-reference/containers/06-filesystem.md +++ b/documents/en/cpp-reference/containers/06-filesystem.md @@ -1,24 +1,25 @@ --- -title: std::filesystem +chapter: 99 +cpp_standard: +- 17 +- 20 +- 23 description: 'Cross-platform file system operation library: path manipulation, directory traversal, and file status queries' -chapter: 99 +difficulty: beginner order: 6 +reading_time_minutes: 2 tags: - host - cpp-modern - beginner -difficulty: beginner -cpp_standard: -- 17 -- 20 -- 23 +title: std::filesystem translation: + engine: anthropic source: documents/cpp-reference/containers/06-filesystem.md source_hash: 31beda2f84b5927c98a29d0ae91c27a14284ad4dee010d9a06404c023839dc85 - translated_at: '2026-05-26T10:14:07.396187+00:00' - engine: anthropic token_count: 633 + translated_at: '2026-05-26T10:14:07.396187+00:00' --- B{Size known at compile time?} + B -- Yes --> C[std::array] + B -- No --> D{Lookup by Key?} + D -- Yes --> E{Need ordered traversal?} + E -- Yes --> F[std::map / std::set] + E -- No --> G[std::unordered_map / std::unordered_set] + D -- No --> H{Frequent insert/delete location?} + H -- Head/Tail --> I[std::deque] + H -- Tail only --> J[std::vector] + H -- Middle (known pos) --> K[std::list] + H -- Rare / Random --> L[std::vector] +``` + +Two supplements. First, if you only need to "borrow for a while" and don't want to transfer ownership, use `std::span`—it's the "unified read-only view for array/vector/C-arrays," the standard for zero-copy parameter passing, detailed in [Deep Dive into span](08-span.md). Second, starting with C++23, there are new options: if you want a "sorted + cache-friendly" map, look at `std::flat_map` (under the hood it's a sorted vector); if you want a "fixed capacity, never heap allocates" variable-length container, look at C++26's `std::dynarray`—we'll cover these two in the [New Standard Containers](10-new-containers-cpp23-26.md) article. + +## Common Mis-selections + +Listing a few high-frequency pitfalls to self-check when picking containers. First, **"Using list because of many insertions/deletions"**—ignoring positioning costs and cache unfriendliness; in most cases, vector plus erase is actually faster. List is only worth it when you truly hold a large number of iterators long-term, and insertions/deletions far outnumber traversals. Second, **unordered containers without reserve**—throwing N elements in without `reserve` triggers multiple rehashes; each rehash re-hashes all elements, wasting cycles in the hot path. Third, **vector repeated push_back without reserve**—same logic, moving the whole block on expansion; a single `reserve` eliminates most copies. Fourth, **passing references across containers ignoring invalidation rules**—especially storing iterators to deque then modifying the container, or erasing while traversing vector without updating the iterator. The compiler won't warn you about these bugs; they blow up at runtime. + +## Wrapping Up + +When picking a container, get three things clear first: operation complexity, memory locality, and iterator invalidation. If these three align, you're 90% there; for details (exception safety, custom allocators, heterogeneous lookup), go back to the specific deep-dive articles. A simple but useful default: **when in doubt, use vector**. It's contiguous, amortized O(1) at the tail, has the most complete interface, and is the safest card with the broadest coverage. Wait until you measure it as a bottleneck before switching. In the next article, we enter container adapters—`std::stack`, `std::queue`, `std::priority_queue`. They aren't new containers, but interface shells wrapping underlying containers into stacks/queues/heaps. + +Want to try it out yourself? Click the online example below (runnable and viewable assembly): + + + +## Reference Resources + +- [Container Library Overview (including iterator invalidation) — cppreference](https://en.cppreference.com/w/cpp/container) +- [Container Iterator Invalidation Rules (by operation) — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) +- [std::vector Iterator Invalidation Section — cppreference](https://en.cppreference.com/w/cpp/container/vector#Iterator_invalidation) diff --git a/documents/en/vol3-standard-library/01-initializer-lists.md b/documents/en/vol3-standard-library/01-initializer-lists.md deleted file mode 100644 index adde72cfd..000000000 --- a/documents/en/vol3-standard-library/01-initializer-lists.md +++ /dev/null @@ -1,244 +0,0 @@ ---- -chapter: 3 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: A detailed explanation of member initializer lists -difficulty: intermediate -order: 1 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 5 -tags: -- cpp-modern -- host -- intermediate -title: Initializer list -translation: - source: documents/vol3-standard-library/01-initializer-lists.md - source_hash: c5f32b513967607a52fde6a6dfa9cc779161a1848aa4ee39dcdc8073b866fe66 - translated_at: '2026-05-26T11:37:01.435396+00:00' - engine: anthropic - token_count: 775 ---- -# Constructor Optimization: Initializer Lists vs. Member Assignment - -In embedded C++ projects, we easily focus our attention on the "visible" areas: interrupts, DMA, timing, cache hit rates, Flash/RAM usage... But for code like constructors that "seem to run only once," we tend to let our guard down subconsciously. - -However, in systems where **objects are created frequently, memory is tight, and construction paths are complex**, how we write constructors directly impacts: - -- Whether redundant construction/destruction occurs -- Whether hidden default initialization costs are introduced -- Whether object invariants are broken -- Whether we "lose optimization space" at compile time - -And these issues **almost all come down to one thing: whether you use initializer lists.** - ------- - -## 1. A Common, But Not "Harmless," Pattern - -When many developers first learn C++, they often write constructors like this: - -```cpp -class Timer -{ -public: - Timer(uint32_t period) - { - period_ = period; - enabled_ = false; - } - -private: - uint32_t period_; - bool enabled_; -}; - -``` - -At first glance, there's nothing wrong with this. The logic is clear, and the readability is fine. - -But in the compiler's eyes, the true meaning of this code is: - -1. `period_` is **default-initialized** -2. `enabled_` is **default-initialized** -3. Enter the constructor body -4. Perform **assignment operations** on both members - -In other words, **members are "processed" at least twice**. - -On desktop platforms, this overhead is usually negligible. But in embedded systems, especially when: - -- A large number of objects are constructed -- Members are structs, arrays, or STL containers -- Construction occurs during the startup phase (Boot / Driver Init) - -This "invisible default initialization" starts to become a very real cost. - ------- - -## 2. Initializer Lists Are Not "Syntactic Sugar" - -Compare this with the initializer list approach: - -```cpp -class Timer -{ -public: - Timer(uint32_t period) - : period_(period) - , enabled_(false) - {} - -private: - uint32_t period_; - bool enabled_; -}; - -``` - -The key change here isn't "writing fewer lines of code," but rather that **the object lifecycle has changed**. Here, our member initialization becomes more direct—**initialization is completed directly during the construction phase**. In other words, **an initializer list is not assignment; it is part of construction**. - -## 3. Some Members Simply "Cannot Be Assigned" - -In embedded systems, this situation is not uncommon. - -#### 1. `const` Members - -```cpp -class Device -{ -public: - Device(uint32_t id) - : id_(id) - {} - -private: - const uint32_t id_; -}; - -``` - -`const` members **can only be assigned once during the initialization phase**. Assignment inside the constructor body is semantically illegal. This isn't a syntax constraint, but rather the language's protection of "object invariants." - ------- - -#### 2. Reference Members - -```cpp -class Driver -{ -public: - Driver(GPIO& gpio) - : gpio_(gpio) - {} - -private: - GPIO& gpio_; -}; - -``` - -Once a reference is bound, it cannot be made to refer to another object. Therefore, **the initializer list is the only correct approach**. - ------- - -#### 3. Members Without Default Constructors - -In your own framework code, this type is actually very common: - -```cpp -class SpiBus -{ -public: - explicit SpiBus(uint32_t base_addr); -}; - -``` - -If such a class exists as a member: - -```cpp -class Sensor -{ -public: - Sensor() - : spi_(SPI1_BASE) - {} - -private: - SpiBus spi_; -}; - -``` - -If we don't use an initializer list here, the code won't even compile. - ------- - -## 4. "Semantic Completeness" Brought by Initializer Lists - -In embedded engineering, we often emphasize that **"an object must be in a usable state once construction is complete."** Initializer lists naturally align with this principle. - -```cpp -class RingBuffer -{ -public: - RingBuffer(uint8_t* buf, size_t size) - : buffer_(buf) - , size_(size) - , head_(0) - , tail_(0) - {} - -private: - uint8_t* buffer_; - size_t size_; - size_t head_; - size_t tail_; -}; - -``` - -This pattern conveys a very clear message: - -> **Once an object is constructed, its internal state is complete and self-consistent.** - -Conversely, splitting initialization across the constructor body actually allows for the existence of a "half-initialized state," which is a very dangerous design signal in low-level systems. - ------- - -## 5. Compiler Optimization Perspective: Initializer Lists = Greater Optimization Space - -From the compiler's perspective: - -- Initializer lists provide **deterministic construction semantics** -- The initial values of members are known during the construction phase -- This makes it easier to perform: - - Constant propagation - - Construction elimination - - Stack object merging - - In some scenarios, even complete object elimination - -Especially when we make heavy use of `constexpr`, `inline`, and templates, **initializer lists are a prerequisite for compile-time optimization**. - ------- - -## Run Online - -Compare the differences between in-body assignment and initializer lists online, and observe how const and reference members are initialized: - - - -## Final Thoughts - -Initializer lists aren't some "advanced technique." They really aren't complicated. In embedded systems, **every redundant initialization translates into real instructions, real Flash, and real time**. Initializer lists are exactly that kind of modern C++ fundamental where **you lose out if you don't write them, and gain steadily if you do**. diff --git a/documents/en/vol3-standard-library/02-array.md b/documents/en/vol3-standard-library/02-array.md new file mode 100644 index 000000000..f4d4d10c6 --- /dev/null +++ b/documents/en/vol3-standard-library/02-array.md @@ -0,0 +1,161 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 'A Deep Dive into `std::array`: Wrapping C Arrays as Aggregate Types + with Zero Overhead, No Pointer Decay, `std::get` and Structured Bindings, Iterators + That Never Invalidate, `constexpr` Compile-Time Lookup, and the Precise Boundary + Between C Arrays and `vector`' +difficulty: intermediate +order: 2 +platform: host +reading_time_minutes: 7 +related: +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- intermediate +- array +- 容器 +title: 'array: A fixed-size aggregate container determined at compile time' +translation: + engine: anthropic + source: documents/vol3-standard-library/02-array.md + source_hash: f41713d84c0a41b88fe22a2838df40e140aeaa4ddab6f72383344f12e67cf698 + token_count: 1337 + translated_at: '2026-06-15T09:11:49.148384+00:00' +--- +# array: A Fixed-Size Aggregate Container for Compile-Time + +## What is array: A Zero-Overhead Aggregate Wrapper for C Arrays + +`std::array` is the "modern shell" that C++11 applied to C arrays. C arrays (`T[N]`) have several old shortcomings: they decay into pointers when passed as arguments (losing length information), lack iterators, cannot be copied or assigned as a whole, and cannot be returned from functions. `std::array` wraps this contiguous memory in a class template, equipping it with STL interfaces, and—crucially—**it is an aggregate type with absolutely no overhead**: the memory layout of `std::array` is identical to that of a C array, with no virtual functions, no vtable pointers, and no extra members. + +```cpp +#include +std::array arr = {1, 2, 3, 4, 5}; +``` + +That `N` is a template parameter, a compile-time constant. This means the size of an array is part of its type—`std::array` and `std::array` are two completely different types and cannot be assigned to each other. The price paid is zero dynamic allocation: the memory occupied by an array is exactly that contiguous block of data, residing on the stack or in the static area, never touching the heap. + +## Precise Comparison with C Arrays: No Decay, Interfaces, and Object Semantics + +Let's count the improvements of `array` over C arrays one by one. First, **it does not decay to a pointer**: a C array passed to a function decays to `T*`, losing its length; an array is an object, so when passed as an argument, it fully preserves its type (including `N`). You either pass by reference `std::array&`, or explicitly provide `data()` to C interfaces. Second, **it has STL interfaces**: `size()`, `empty()`, `begin()` / `end()`, `front()`, `back()`, and `at()`, allowing it to be fed directly to algorithms and range-based for loops. Third, **it supports copy and assignment**: copy construction copies elements one by one, and it can be used as a return value or a class member—things C arrays cannot do. + +```cpp +void func(std::array& a) { + // a.size() is 5, type is preserved + // No decay to int* +} +``` + +But underneath, it is still that same contiguous memory. The standard guarantees that `array` is an aggregate, so `sizeof(std::array)` equals `sizeof(T) * N` (no extra members, no waste other than potential tail padding). It has no overhead, simply adding interfaces and type safety. + +## The Boundary with vector: When to Use Fixed Size + +The dividing line between `array` and `vector` comes down to one thing: **is the size known at compile time?** If the size is fixed at compile time and won't change, use `array`—zero heap allocation, zero overhead, can be made `constexpr`, and saves RAM if placed in a static area. If the size is determined at runtime or requires insertion/deletion, use `vector`. + +The trade-offs are equivalent: the size of an `array` is part of its type (`std::array` and `std::array` are not interchangeable), so a function accepting "an int array of any size" cannot use `array` (you would need `std::span` or templates); `vector` doesn't have this limitation but incurs heap allocation and reallocation overhead. In short: **fixed size uses `array`, variable size uses `vector`**. For the middle ground (size known at runtime but avoiding heap allocation), wait for C++26's `std::dynarray`, or manage a buffer yourself with `std::span`. + +## Privileges of Being an Aggregate: std::get, Structured Bindings, and Tuple Interface + +Because `array` is an aggregate type, it enjoys "tuple-like" benefits beyond C arrays. `std::get` can access elements by compile-time index (returning a reference with type safety); C++17 structured bindings can unpack a small array directly into variables; `std::tuple_size` and `std::tuple_element` also recognize `array`, meaning it can be slotted into generic code that consumes tuple-like types. + +```cpp +std::array coord = {10, 20, 30}; +auto& [x, y, z] = coord; // Structured binding +static_assert(std::tuple_size::value == 3); +``` + +None of this works with C arrays—C arrays can't use `std::get` and don't support structured bindings. For small arrays with "a fixed number of values" (like 3D coordinates or RGB), `array` plus structured binding is even smoother than writing a custom struct. + +## Complexity, Iterator Invalidation, and Exception Safety + +Complexity is straightforward: random access (`operator[]`) and `at()` are both O(1), traversal is O(n), and there is no reallocation or resizing because the size is fixed. + +Regarding **iterator invalidation**, `array` is the most worry-free: iterators never invalidate. Because `array` is a fixed-size aggregate with no resizing or insertion/deletion (the interface lacks `push_back` / `insert`), iterators, references, and pointers remain valid as long as the array object itself is alive. This is cleaner than `vector` (invalidation on resize), `deque`, or `list`. + +For exception safety, note that `at()` performs bounds checking and throws `std::out_of_range` if out of bounds; `operator[]` does not check, so out-of-bounds access is undefined behavior. In environments with exceptions disabled (like `-fno-exceptions`), `at()`'s check might degrade to a no-op or abort, so in those scenarios, use `operator[]` and ensure indices are correct yourself. + +## Let's Run It: Zero Overhead and constexpr + +Saying "zero overhead" isn't enough; let's run it to see. First, confirm that `sizeof` is truly the same as a C array: + +```cpp +#include +#include + +int main() { + std::array arr = {1, 2, 3, 4, 5}; + int c_arr[5] = {1, 2, 3, 4, 5}; + + static_assert(sizeof(arr) == sizeof(c_arr), "Sizes must match"); + std::cout << "sizeof(array): " << sizeof(arr) << std::endl; + std::cout << "sizeof(c_arr): " << sizeof(c_arr) << std::endl; + + // Verify data() points to the first element + static_assert(sizeof(arr) == 5 * sizeof(int)); + return 0; +} +``` + +```text +sizeof(array): 20 +sizeof(c_arr): 20 +``` + +`sizeof` is completely equal, with no overhead—`array` is just that contiguous memory wrapped in a class. `data()` indeed points to the first element, so it can be safely handed to C interfaces or DMA. + +Another major feature of `array` is **`constexpr`**—it can complete initialization and computation at compile time, placing the generated data directly into the read-only section. A classic use case is generating a CRC lookup table at compile time: + +```cpp +#include +#include + +constexpr std::array generate_crc_table() { + std::array table{}; + for (uint16_t i = 0; i < 256; ++i) { + uint16_t crc = i; + for (int j = 0; j < 8; ++j) { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc >>= 1; + } + table[i] = crc; + } + return table; +} + +// Computed at compile time, stored in Flash +constexpr auto crc_table = generate_crc_table(); +``` + +This 256-entry table is calculated at compile time. When the program runs, it reads directly from the read-only section, consuming neither RAM nor runtime CPU. This "compile-time lookup" is the golden combination of `array` + `constexpr`—C arrays with `constexpr` can't achieve this as cleanly (especially when involving copy returns). + +## Extensions: array in Embedded Systems (DMA / Flash / Stack) + +Because `array` involves zero heap allocation, guarantees contiguous memory, and supports `constexpr`, it is particularly popular in embedded systems. Here are a few practical points (beyond the main thread, use as needed). First, **contiguous memory guarantee**: the pointer returned by `data()` points to contiguous storage, which can be safely handed to DMA or HAL, provided the element type is trivially copyable. Second, **save RAM by using static storage**: use `static` for large arrays or place them in namespace scope; use `constexpr` for lookup table data to go directly to Flash, saving RAM. Third, **stack depth**: small arrays on the stack are fine, but be mindful of task / ISR stack depth limits—don't put a large `array` on a narrow stack. + +## Wrapping Up + +`array` is the modern shell for C arrays: zero overhead, STL interfaces, no decay, usable as an object, and benefiting from `std::get` and structured bindings via its aggregate nature. Its iterators never invalidate, it supports `constexpr`, and it has zero heap allocation—as long as the size is fixed at compile time, it is a more suitable choice than both C arrays and `vector`. In the next article, we look at its "dynamic version", `vector`, moving from fixed to variable size, at the cost of the heap and reallocation. + +Want to try running it immediately? Check out the online example below (runnable, with assembly view): + + + +## References + +- [std::array — cppreference](https://en.cppreference.com/w/cpp/container/array) +- [Aggregate type — cppreference](https://en.cppreference.com/w/cpp/language/aggregate_initialization) +- [Container iterator invalidation rules summary — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/en/vol3-standard-library/02-span.md b/documents/en/vol3-standard-library/02-span.md deleted file mode 100644 index d9225006b..000000000 --- a/documents/en/vol3-standard-library/02-span.md +++ /dev/null @@ -1,234 +0,0 @@ ---- -chapter: 7 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: C++20 Array View -difficulty: intermediate -order: 2 -platform: host -prerequisites: -- 'Chapter 6: RAII与智能指针' -reading_time_minutes: 8 -tags: -- cpp-modern -- host -- intermediate -title: std::span Array View -translation: - source: documents/vol3-standard-library/02-span.md - source_hash: 8894624edd7ee1cf4674210cb203bb69517deb95bfef2ceab3847e230b4e109f - translated_at: '2026-05-26T11:37:07.215778+00:00' - engine: anthropic - token_count: 1265 ---- -# Embedded C++ Tutorial: std::span—A Lightweight, Non-owning Array View - -Think of `std::span` as a "transparent conveyor belt" in C++: it doesn't own the cargo on it (memory), but calmly and efficiently tells you "how many elements are here and where they start." In embedded systems, we often need to pass a chunk of memory to a function—without copying it, and without losing type or boundary information. `std::span` was born for exactly this scenario. - -Or rather, it wasn't until C++20 that a standard view container finally appeared. - -- `std::span` is a **non-owning** view: it is not responsible for freeing memory. -- It is typically a pointer plus a length (very lightweight, cheap to copy). -- Using `std::span` as a function parameter elegantly accepts `T[]`, `std::array`, `std::vector`, raw pointer plus length, and more. -- **Key caveat**: Do not let a `span` outlive the underlying data—a dangling pointer will still bite you. - ------- - -## Motivation: Why not just use a pointer or vector? - -In embedded code, we often see function signatures like this: - -```cpp -void process_buffer(uint8_t* buf, size_t n); - -``` - -This approach is indeed flexible, but it has a downside: the reader has to simultaneously remember the type of `buf`, whether the length unit is "number of elements" or "number of bytes", and whether the function modifies the data... There are simply too many places for things to go wrong. `std::span` makes these semantics explicit: the type and value (length) are bundled in the same object, improving both readability and safety. - ------- - -## Basic Usage - -```cpp -#include -#include -#include -#include - -void print_bytes(std::span s) { - for (auto b : s) std::cout << std::hex << int(b) << ' '; - std::cout << std::dec << '\n'; -} - -int main() { - uint8_t buffer[] = {0x10, 0x20, 0x30}; - std::vector v = {1,2,3,4}; - std::array a = {9,8,7}; - - print_bytes(buffer); // 从内置数组构造 - print_bytes(v); // 从 vector 构造 - print_bytes(a); // 从 std::array 构造 - print_bytes({v.data(), 2}); // 从 pointer + size 构造 -} - -``` - -`print_bytes` uses `std::span` to receive input: it states that the content won't be modified, accepts multiple container sources, and requires the caller to copy no data. - ------- - -## Dynamic vs. Static Extent - -`std::span` has two forms: - -- `std::span` (or `std::span`): runtime size; -- `std::span`: compile-time fixed element count `N` (known as a static extent). - -Example: - -```cpp -int arr[4]; -std::span s_fixed(arr); // 只有长度为 4 的数组能绑定 -std::span s_dyn(arr, 4); // 任意长度,运行时记录 - -``` - -A static `Extent` can enable extra compile-time checks or optimizations in certain scenarios, but in embedded systems, a dynamic extent is more common (since buffer lengths are often determined at runtime). - ------- - -## Useful Member Functions - -```cpp -s.size(); // 元素个数 -s.size_bytes(); // 字节数(注意!元素个数 * sizeof(T)) -s.data(); // 指向首元素的指针(可能为 nullptr 当 size()==0) -s.empty(); -s.front(), s.back(); -s[i]; // 下标,不做运行时检查(与 operator[] 语义一致) -s.subspan(offset, count); // 切片,返回新的 span(仍为 non-owning) -s.first(n), s.last(n); // 前 n 个或后 n 个元素视图 -std::as_bytes(s); // 将 span 视为 span -std::as_writable_bytes(s); // 视为 span(当 T 可写时) - -``` - -Note: `operator[]` does not perform bounds checking; if you need boundary checks, use a `at`-like wrapper or add assertions during debugging. - ------- - -## Advanced Example: subspan and Byte Operations - -```cpp -#include -#include // for std::byte - -void recv_packet(std::span buffer) { - if (buffer.size() < 4) return; - auto header = buffer.first(4); - uint16_t len = header[2] | (header[3] << 8); - - if (buffer.size() < 4 + len) return; - auto payload = buffer.subspan(4, len); - - // 把 payload 当作字节流传给 CRC 函数 - auto bytes = std::as_bytes(payload); - // crc_check(bytes.data(), bytes.size()); // 示例:调用检验函数 -} - -``` - -This pattern of slicing an overall buffer into header/payload is especially well-suited for embedded protocol parsing—concise and safe (as long as you ensure the incoming `buffer` is valid). - ------- - -## Best Practices for Function Parameters - -Designing an API to accept `std::span` offers several benefits: - -- The caller can pass an array, `std::array`, `std::vector`, or a raw pointer plus length; -- The function signature clearly expresses "this is a view (possibly read-only)"; -- The function doesn't need template generics to support various containers. - -Example: - -```cpp -void process(std::span data); // 明确:不修改数据 -void mutate(std::span data); // 明确:会修改数据 - -``` - -This is more intuitive than writing `template void process(const Container& c)`, and it avoids unnecessary compilation bloat. - ------- - -## Common Pitfalls - -1. **Dangling views**: The most common mistake. Do not bind a `std::span` to a local `std::vector`'s `data()` and return it to the caller: - - ```cpp - std::span bad() { - std::vector v = {1,2,3}; - return v; // ❌ v 被销毁,返回的 span 悬垂 - } - - ``` - -1. **Assuming ownership**: A span does not hold memory, and it will not destruct or free it. If you need ownership, use `std::vector`, `unique_ptr`, etc. - -1. **Improper byte views**: `std::as_bytes` returns a `span` for read-only byte access; use `as_writable_bytes` only when the underlying data is writable. - -1. **Out-of-bounds access**: `operator[]` does not check boundaries. Perform explicit checks or use debug assertions when necessary. - -1. **Not a null-terminated string**: A `std::span` is not a `C` string and does not guarantee termination with `'\0'`. For string handling, use `std::string_view` or process with an explicit length. - ------- - -## Comparison with `std::string_view` - -- `std::string_view` is designed specifically for character sequences (read-only view) and carries string semantics (commonly used for text). -- `std::span`/`std::span` are generic for any element type, including writable scenarios. - When handling binary protocols/buffers, `std::span` is more appropriate; when dealing with immutable text, `string_view` is more semantic. - ------- - -## Quick Embedded Scenarios - -- A DMA callback places data into a fixed buffer, and the callback passes a `std::span` to the processing function without copying. -- Data is read from Flash into a buffer, and then `std::span` is used to slice and parse the header and blocks. -- Passing small chunks of data in an interrupt or real-time path, where the copy overhead of a `span` is extremely low. - ------- - -## Code Tips - -1. Write function parameters as `std::span` to express read-only intent. -2. If you want to accept a buffer of size N without changing the logic, you can accept a `std::span` (static extent). -3. Use `subspan`, `first`, and `last` to construct subviews, rather than manually calculating pointer offsets. -4. Explicitly state in your public API documentation: **a span does not manage lifetimes**. - ------- - -## Run Online - -Experience std::span online by constructing views from different container types and performing subspan slicing operations: - - - -## Quick API Reference - -`s` for `std::span`: - -- `s.size()`, `s.size_bytes()`, `s.data()`, `s.empty()` -- `s[i]` (no bounds checking), `s.front()`, `s.back()` -- `s.begin()`, `s.end()` (supports range-for) -- `s.subspan(offset, count)`, `s.first(n)`, `s.last(n)` -- `std::as_bytes(s)`, `std::as_writable_bytes(s)` diff --git a/documents/en/vol3-standard-library/01-vector-deep-dive.md b/documents/en/vol3-standard-library/03-vector-deep-dive.md similarity index 99% rename from documents/en/vol3-standard-library/01-vector-deep-dive.md rename to documents/en/vol3-standard-library/03-vector-deep-dive.md index ab8d3a084..9668313c3 100644 --- a/documents/en/vol3-standard-library/01-vector-deep-dive.md +++ b/documents/en/vol3-standard-library/03-vector-deep-dive.md @@ -13,7 +13,7 @@ order: 1 platform: host prerequisites: - 卷一:vector 基础用法(size / capacity / push_back) -reading_time_minutes: 16 +reading_time_minutes: 14 tags: - host - cpp-modern @@ -21,11 +21,11 @@ tags: - vector title: 'Vector Deep Dive: Three Pointers, Reallocation, and Iterator Invalidation' translation: - source: documents/vol3-standard-library/01-vector-deep-dive.md - source_hash: 3a794c3b8b7c339211aacff5c51798b07990dff9266fcf14a11fb79bdaa0a358 - translated_at: '2026-06-14T00:19:27.289156+00:00' engine: anthropic + source: documents/vol3-standard-library/03-vector-deep-dive.md + source_hash: 3a794c3b8b7c339211aacff5c51798b07990dff9266fcf14a11fb79bdaa0a358 token_count: 2821 + translated_at: '2026-06-14T00:19:27.289156+00:00' --- # Vector Deep Dive: Three Pointers, Reallocation, and Iterator Invalidation @@ -272,7 +272,7 @@ Of course, you can also click this to see the phenomenon! control; // 中控数组,每项指向一个块 + // 每个 Block 是一段连续内存,装若干元素 +}; +// 随机访问:block = control[i / chunk_size],元素 = block[i % chunk_size] +``` + +This structure brings three characteristics. First, **push/pop at both the head and tail are O(1)**: when the tail is full, a new chunk is added; when the head is full, a chunk is added in front (or filled backward within the chunk). Existing elements are not moved—this is its biggest advantage over `vector`. Second, **random access is still O(1)**; `deque` calculates which chunk the element is in, then takes the offset within that chunk. It only involves one extra pointer dereference ("central control → chunk") compared to `vector`, so it is slightly slower. Third, **reallocation does not move all elements**: when a `deque` is full, we only need to expand the central control array (a set of pointers, which is small) and hang new chunks. The addresses of existing elements remain unchanged—this is much gentler than `vector` reallocation (which moves everything and invalidates all iterators). + +The price is that memory is not in a single block (unfriendly for scenarios requiring passing data to C interfaces or needing a continuous buffer), and the "central control + multiple chunks" structure itself has some space overhead. + +## list: Doubly linked list, O(1) insertion/deletion in the middle + splice + +`list` is a doubly linked list, where each node stores `prev` and `next` pointers. Its core selling point is: **insertion and deletion at known positions (having an iterator) is O(1)**—it only changes a few pointers and does not move any other elements. Furthermore, **iterators never become invalid** (insertion/deletion only affects the iterator of the deleted node itself), something even `deque` and `vector` cannot achieve. + +`list` also has a unique skill called **splice**: `l1.splice(pos, l2)` can directly "splice" the node chain from `l2` into `l1`. The whole process is O(1) and copies no elements—this is a capability unique to linked lists that contiguous containers cannot offer. It is suitable for scenarios like "moving a segment of one list to another at zero cost." + +However, the shortcomings of `list` are also critical. First, **it does not support random access**, there is no `operator[]`, so finding the 1000th element requires walking 1000 steps from the head (O(n)). Second, **it is extremely cache-unfriendly**: nodes are scattered all over the heap. During traversal, CPU prefetching fails and cache misses occur frequently. Later, we will run a demo to show you that traversing a `list` is several times slower than a `vector`, and this is the reason. Therefore, the advantage of "O(1) insertion in the middle" is often offset by "O(n) to find the position" plus "slow traversal"—unless you are actually holding an iterator and inserting/deleting frequently, it might not be worth it. + +## forward_list: The extremely memory-efficient singly linked list + +`forward_list` is a singly linked list, where each node only stores a `next` pointer, saving one predecessor pointer compared to `list`. It was introduced in C++11 with a clear goal: to match the "zero overhead" of hand-written C singly linked lists—when you only need forward traversal and are memory sensitive (e.g., in embedded systems), there is no need to pay the price of an extra pointer for reverse capabilities you don't use. + +The trade-off is naturally that you cannot traverse backwards, and **there is no O(1) `size()`** (you have to walk O(n) to the end), only `empty()` is O(1). The interface is also more streamlined than `list`: it **deliberately does not provide a `size()` member function**—because the standard requires `size()` to be O(1), and a singly linked list cannot maintain this in O(1), so it simply isn't provided. If you need it, you have to count yourself. + +## Let's run it: Traversal vs. Head Insertion, Two Completely Opposite Faces + +Saying "list traversal is slow" and "vector head insertion is slow" is too abstract. Let's just run it. First, look at traversal: `vector`, `deque`, and `list` each hold one million `int`s, and we traverse to sum them up. + +```cpp +#include +#include +#include +#include +#include + +int main() +{ + const int N = 1000000; + std::vector v(N); + std::deque d(N); + std::list l; + for (int i = 0; i < N; ++i) { + v[i] = i; + d[i] = i; + l.push_back(i); + } + + volatile long long sink = 0; + auto bench = [&](auto& c, const char* name) { + auto t0 = std::chrono::high_resolution_clock::now(); + long long s = 0; + for (auto x : c) { + s += x; + } + sink = s; + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << name << ": " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + }; + + bench(v, "vector "); + bench(d, "deque "); + bench(l, "list "); + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/traversal /tmp/traversal.cpp && /tmp/traversal +``` + +```text +vector : 0.3 ms +deque : 0.44 ms +list : 1.9 ms +``` + +(GCC 16.1.1, local machine; the magnitude relationship is stable.) `list` is six times slower than `vector` and four times slower than `deque`—this is the real cost of scattered nodes and cache unfriendliness. Because `deque` is segmented continuous, there is still locality within chunks, so it is significantly faster than `list`, but still slightly slower than `vector` which is one single contiguous block. + +Now look at a reversed scenario: inserting one hundred thousand elements at the head. + +```cpp +#include +#include +#include +#include +#include + +int main() +{ + const int N = 100000; + volatile int sink = 0; + + { + std::vector v; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + v.insert(v.begin(), i); // 每次 O(n) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "vector front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = v.size(); + } + { + std::deque d; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + d.push_front(i); // O(1) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "deque front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = d.size(); + } + { + std::list l; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + l.push_front(i); // O(1) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "list front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = l.size(); + } + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/front_insert /tmp/front_insert.cpp && /tmp/front_insert +``` + +```text +vector front insert: 246 ms +deque front insert: 0.2 ms +list front insert: 4.8 ms +``` + +This time it is completely reversed: `vector` head insertion takes 246ms, while `deque` only takes 0.2ms—a difference of over a thousand times. This is because every `vector::insert` at the head has to move all elements back by one position. Doing this 100,000 times results in O(n²); `deque` and `list` head insertions are both O(1). Note that `deque` is even faster than `list` (`list` has to `malloc` a node every time, while `deque` just fills within a chunk and occasionally adds a chunk). This is also why `deque` beats `list` in "double-ended addition/deletion" scenarios. + +Putting these two sets of data together makes it clear: **there is no silver bullet**. Use `vector`/`deque` for traversal-intensive tasks, and `deque`/`list` for frequent head/middle insertion. Choosing wrong leads to order-of-magnitude performance differences. + +## Finally, a summary: How to choose + +| Requirement | Choice | +|------|----| +| Random access + mainly tail add/delete | `vector` | +| Add/delete at both ends (queue / double-ended) | `deque` | +| Frequent insert/delete at known positions / need splice / iterators must not invalidate | `list` | +| Extreme memory saving + forward traversal only (embedded) | `forward_list` | + +A mnemonic: use `vector` if you can, use `deque` if you really need double-ended, and use `list` / `forward_list` only if you really need linked list features. Among sequential containers, `vector` is almost always the default answer; the other three are specialized tools to "swap in only when there is a clear requirement." We have finished covering associative containers like `map` and `unordered_map`. In the next article, we will leave containers behind and look at the standard library's iterator and algorithm system. + +Want to try running it directly to see the effect? Click the online example below (you can run it and see the assembly): + + + +## Reference Resources + +- [std::deque — cppreference](https://en.cppreference.com/w/cpp/container/deque) +- [std::list — cppreference](https://en.cppreference.com/w/cpp/container/list) +- [std::forward_list — cppreference](https://en.cppreference.com/w/cpp/container/forward_list) +- [Container Iterator Invalidation Rules Summary Table — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/en/vol3-standard-library/05-object-size-and-trivial-types.md b/documents/en/vol3-standard-library/05-object-size-and-trivial-types.md deleted file mode 100644 index e9c8444b0..000000000 --- a/documents/en/vol3-standard-library/05-object-size-and-trivial-types.md +++ /dev/null @@ -1,278 +0,0 @@ ---- -chapter: 3 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: Exploring C++ object memory layout -difficulty: intermediate -order: 5 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 12 -tags: -- cpp-modern -- host -- intermediate -title: Object Size and Trivial Types -translation: - source: documents/vol3-standard-library/05-object-size-and-trivial-types.md - source_hash: f5c43a40fc7f0fc41bb19999da5fe2b0d173054f31d8622cf9a5f81d70468449 - translated_at: '2026-05-26T11:38:14.483343+00:00' - engine: anthropic - token_count: 1747 ---- -# Modern C++ for Embedded Systems Tutorial: Object Size, Memory Alignment, Trivial/Standard-Layout Types, and Aggregate Initialization - -When writing low-level code, building embedded systems, or interfacing with C APIs, we often get tripped up by a string of obscure terms: `alignas`, `alignof`, `sizeof`, aggregates, and more. These concepts might seem fragmented, but they actually form an interconnected map: they dictate an object's memory representation, copy semantics, whether we can safely use `memcpy`, whether a type is ABI-compatible with C structs, and initialization flexibility. - ------- - -## Starting with "Size" and "Alignment": Why `sizeof` Isn't Always the Sum of Members - -`sizeof` reports the number of bytes an object occupies in memory (i.e., its full object representation, which includes necessary padding), while `alignof` reports the type's **alignment constraint**—meaning the object's starting address must be a multiple of `alignof`. - -Imagine a building (the object) with different rooms (members) of varying sizes and alignment rules. To fit certain large items correctly, we might need gaps between floors (padding). To the compiler, these gaps are mandatory. - -Let's look at a common example: - -```cpp -struct A { - char c; // 1 byte - int i; // 4 bytes, alignment 4 -}; -// typical layout on common ABIs: -// offset 0: c -// offset 1..3: padding -// offset 4..7: i -// sizeof(A) == 8 - -``` - -If we rearrange the order: - -```cpp -struct B { - char a; // offset 0 - int i; // offset 4 (padding 3 bytes) - char b; // offset 8 - // padding 3 bytes to make sizeof multiple of alignof(B) (which is 4) - // sizeof(B) == 12 -} - -``` - -Placing the two `uint32_t` members together usually reduces padding: - -```cpp -struct C { - char a; - char b; - int i; - // char a@0, char b@1, padding@2-3, int@4..7 -> sizeof == 8 -} - -``` - -Therefore, reordering members and grouping wide-aligned members (like `uint64_t`, `double`, SIMD vectors, etc.) together or placing them at the end of a struct is a common memory compaction strategy. For embedded systems, this can often squeeze considerable space out of unnecessary RAM usage. - -Additionally, a struct's overall alignment is the **largest alignment** among its members. The compiler also adds tail padding at the end of the struct to ensure that `sizeof` is a multiple of `alignof`. This affects the spacing between array elements and how structs are laid out in arrays. - -We can use `alignas` to force or change alignment, for example, specifying alignment for a SIMD buffer that requires 16-byte alignment: - -```cpp -struct alignas(16) Vec4 { - float x,y,z,w; // sizeof == 16, alignof == 16 -}; - -``` - -We need to be careful with `alignas`: increasing alignment changes the struct's ABI and `sizeof`, and can expose unaligned access issues on certain platforms (if we place an object at an unaligned address on unsupported hardware, it will crash). - ------- - -## trivial / trivially_copyable / standard-layout: Why These "Type Traits" Matter - -The C++ standard breaks down a set of type characteristics to precisely express "how an object of this type behaves in memory." This design, starting in C++11 (which split the historical POD concept into several distinct properties), is especially important for embedded and systems programming because it dictates whether we can use `memcpy`, interoperate with C, and what optimization opportunities exist. - -Let's first put a few frequently confused terms into a natural language map: - -- **trivial type**: Broadly speaking, a type with "trivial" special member functions (default constructor, copy/move constructors, assignment operators, and destructors are all compiler-generated without custom logic). In other words, construction, copying, and destruction execute no runtime code—the object's bit pattern is its object representation, with no hidden actions. -- **trivially_copyable type**: Objects of this type can be safely copied via byte-by-byte copying (`memcpy`). After copying, the target object has the same object representation and can be properly destroyed, etc. `is_trivially_copyable` is the key criterion for determining whether `memcpy` can be used. -- **standard-layout type**: This type has predictable memory layout rules (e.g., non-static data members are arranged in declaration order, providing certain guarantees for C interoperability). It avoids unpredictable memory layouts caused by complex access control, virtual inheritance, or multiple base classes. - -A crucial fact is that the old concept `POD` (Plain Old Data) was split in C++11 into `is_trivial` and `is_standard_layout`; semantically, `is_pod` is simply "both trivial and standard-layout." Many safety assumptions related to ABI and C interoperability can be checked using `is_trivially_copyable` and `is_standard_layout`. - -Why is this information useful? Because it directly affects: - -- Whether we can read/write an object as a byte sequence (e.g., saving it to flash, or transferring it directly from memory via DMA). - - **Only types that are `is_trivially_copyable` can safely use `memcpy` to copy the object representation**. -- Whether we can pass a C++ type as a C `struct` to an external C interface (e.g., device register mappings, bootloader data structures). - - **This typically requires `is_standard_layout` to guarantee layout compatibility**. -- How the type behaves in constant expression and zero-initialization contexts (e.g., static storage duration object initialization and memory images). - -Let's look at an example combining these concepts: - -```cpp -struct S { - int x; - double y; - // 没有用户定义构造/析构/拷贝、没有虚函数、没有基类…… -}; -// S 通常是 trivial、trivially_copyable、standard-layout -> POD -static_assert(std::is_trivially_copyable_v); -static_assert(std::is_standard_layout_v); - -``` - -Compare this with a non-trivial type: - -```cpp -struct T { - T() { /* do something */ } // user-provided ctor - int x; -}; -// T 不是 trivial(因为用户定义了构造函数);可能也不是 trivially_copyable。 - -``` - -To reiterate an easily misunderstood point: **trivial ≠ trivially_copyable**. The former emphasizes the "triviality" of special members (especially the default constructor), while the latter emphasizes whether byte-by-byte copying is safe. In practice, to determine whether we can `memcpy`, we should use `is_trivially_copyable`. - ------- - -## Aggregates and Aggregate Initialization: From Braces to C++20 Designated Initializers - -An aggregate is a highly convenient type category: it allows us to initialize objects by directly listing members inside braces (aggregate initialization). This is extremely intuitive when writing data descriptions (like device descriptor tables or configuration structs), and it naturally suits `constexpr` and static initialization. - -A classic aggregate (described intuitively) is "a type with no user-defined constructors, no virtual functions, all non-static data members are public, and no base classes (or it meets the standard-layout restrictions)"—in short, the compiler can simply treat aggregate initialization as copying values into the object representation member by member in order. - -Example: - -```cpp -struct Point { int x, y; }; -Point p1 { 1, 2 }; // aggregate initialization, 成员按声明顺序赋值 - -``` - -One benefit of aggregate initialization is that it allows partial initialization (the remaining members will be default-initialized/zero-initialized, depending on the context), and it is commonly used in `constexpr`: - -```cpp -struct Config { - int baud; - int parity; - int stop_bits; -}; - -constexpr Config default_cfg { 115200, 0, 1 }; - -``` - -### C++20 Designated Initializers: More Readable and Robust - -C's "designated initializers" were officially introduced into C++20 as a formal language feature. This makes aggregate initialization more readable, insensitive to member order, and easier to maintain (adding new members won't break old code due to ordering issues). - -Usage example: - -```cpp -struct S { - int a; - int b; - int c; -}; - -S s1 { .b = 2, .a = 1, .c = 3 }; // 成功:成员顺序不重要 -S s2 { .a = 1 }; // 只初始化 a,b 和 c 会做默认初始化(对内置类型通常为未定义或零,取决上下文) - -``` - -Designated initializers also support nested structs and array index designations (similar to C's `.name = value` and `[index] = value`)—this is highly practical for initializing complex hardware description data structures, register layouts, or long tables. Here is a more hardware-oriented example: - -```cpp -struct Header { - uint16_t id; - uint16_t flags; -}; - -struct Packet { - Header hdr; - uint8_t payload[8]; -}; - -Packet pkt { - .hdr = { .id = 0x1234, .flags = 0x1 }, - .payload = { [0] = 0xAA, [3] = 0x55 } // 只给第 0 和第 3 个元素赋值 -}; - -``` - -This brings several practical benefits: - -- Significantly improved readability: seeing `.baud_rate = 115200` makes the meaning clear, rather than guessing by position. -- Resilience to extension: Adding new members won't break old code (unless the old code relies on positional order). -- Better compatibility with C (making it easier to port C-style initialization paradigms to C++). - -Note: designated initializers only apply to **aggregate types**. We cannot use this syntax for classes with user-defined constructors. - ------- - -## Connecting the Dots: How Embedded and Low-Level Engineers Apply This Knowledge - -Now let's string the points above into some practical, actionable principles, written as a continuous narrative to help us avoid pitfalls and write more robust code when doing embedded C++. - -When defining data structures that interact with C (such as device register layouts, bootloader metadata, serialization formats, or DMA buffers), we usually need to ensure the type is **standard-layout** (to guarantee a predictable memory layout) and ideally **trivially_copyable** (to easily `memcpy` or interpret a block of memory as that struct). When defining them, avoid virtual functions, avoid private non-static data members, and do not write custom constructors/destructors/copy operations. For important assertions, use `static_assert`: - -```cpp -static_assert(std::is_standard_layout_v, "MyRegs must be standard-layout for C-ABI compatibility"); -static_assert(std::is_trivially_copyable_v, "MyRegs must be trivially_copyable for memcpy usage"); - -``` - -Memory alignment affects `sizeof` and array layout. If our hardware or DMA requires special alignment (e.g., 16-byte aligned cache lines or SIMD), we should use `alignas` to specify it explicitly, noting that this changes `sizeof` and the ABI. For example, a struct decorated with `alignas(16)` will occupy a multiple of 16 bytes for each element in an array. - -When writing initialization code, we should prefer brace initialization and C++20 designated initializers. This not only makes the code readable but also reduces bugs introduced by changes in member order. It is particularly safe and intuitive for registers or configuration tables. For example: - -```cpp -struct DeviceConfig { - uint32_t mode; - uint32_t timeout_ms; - uint8_t flags; -}; - -DeviceConfig cfg { - .mode = 3, - .timeout_ms = 1000, - // .flags 未指定 -> 按规则零/默认初始化 -}; - -``` - -When we need to save RAM, remember that rearranging fields can significantly reduce struct size, especially in scenarios with large numbers of objects or arrays. Place wide-aligned members (`uint64_t`, `double`, SIMD) at the beginning of the struct or close together, and group small-byte members together to avoid interleaving that causes multiple padding instances. Always use `sizeof` and `alignof` to verify our assumptions, and use `static_assert` to encode those assumptions at compile time when necessary. - -Finally, regarding an object's copy semantics: **only when a type is `is_trivially_copyable` is it safe to binary-copy it to another object (e.g., via `memcpy`)**. Do not perform binary copies on classes containing virtual functions, non-trivial destructors, or special members; for these types, use constructor/copy/assignment semantics. - ------- - -## Run Online - -Experience memory alignment and padding, type traits checks, and C++20 designated initializers online: - - - -## Summary - -- `alignof` determines an object's alignment requirements; `sizeof` reports how many bytes an object actually occupies in memory (including padding). -- Internal padding within an object comes from alignment rules; arranging member order thoughtfully can reduce padding and save RAM. -- `is_trivial`, `is_trivially_copyable`, and `is_standard_layout` are the standard's fine-grained divisions of type characteristics: - - To use `memcpy` or save a binary image, ensure `is_trivially_copyable`. - - To guarantee layout compatibility with C, ensure `is_standard_layout`. - - `is_pod` is conceptually just both `is_trivial` and `is_standard_layout`. -- Aggregate initialization is very convenient; C++20 designated initializers make initialization safer, more readable, and less dependent on member order. -- In embedded and low-level scenarios, we should at least use `static_assert` to check these invariants (size, alignment, whether trivially_copyable/standard-layout) at interface boundaries. Code built this way is both efficient and robust. diff --git a/documents/en/vol3-standard-library/06-custom-allocators.md b/documents/en/vol3-standard-library/06-custom-allocators.md deleted file mode 100644 index d6e879d24..000000000 --- a/documents/en/vol3-standard-library/06-custom-allocators.md +++ /dev/null @@ -1,198 +0,0 @@ ---- -chapter: 7 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: Custom STL allocator -difficulty: intermediate -order: 6 -platform: host -prerequisites: -- 'Chapter 6: RAII与智能指针' -reading_time_minutes: 8 -tags: -- cpp-modern -- host -- intermediate -title: Custom Allocator -translation: - source: documents/vol3-standard-library/06-custom-allocators.md - source_hash: 73c2d198fac8c850e3849d204355a05b5b7fa2c4e94f8e5d380b349e37d03617 - translated_at: '2026-05-26T11:37:53.288166+00:00' - engine: anthropic - token_count: 1185 ---- -# Modern C++ for Embedded Systems Tutorial — Custom Allocators - -In the embedded world, memory isn't an "infinite" set of drawers, but rather a suitcase that always seems to complain about how much space you're taking up. Are the default `new` / `malloc` friendly to us? Sometimes they are (yes, they're convenient); but more often, they are latent performance bombs, sources of unpredictable latency, and breeding grounds for fragmentation. Therefore, writing a "custom allocator"—your own memory management strategy—becomes an essential rite of passage for engineers. - ------- - -## Why Customize an Allocator? - -Imagine these scenarios: a real-time task cannot be blocked by sporadic `malloc`; the startup phase requires allocating several objects at once to avoid runtime allocation; small objects are allocated frequently but at a constant size; or you want to carve out a large block of memory for a specific module to make tracking and reclamation easier. Default allocators often fail to satisfy all of these simultaneously: determinism, low memory footprint, low fragmentation, and high performance. - -Custom allocators modify the specific patterns of memory requests, allowing us to plug in our own fixed-size pools, stack allocators, or fast allocators. As discussed in previous blog posts, these implementations can effectively avoid heap fragmentation and improve locality. - ------- - -## Fundamental Concepts of Allocators - -An allocator, at its core, boils down to two things: **allocation** (providing a chunk of unused memory) and **deallocation** (returning memory to the pool). In C++, we also need to pay attention to alignment and object construction/destruction (placement `new`, explicit `destroy`). - -Common strategies include: **Bump (pointer-bumping) allocators**, **Free-list (memory pool) allocators**, **Stack allocators**, and more complex approaches like **TLSF / hierarchical bitmaps**. Let's compare them directly through code. - ------- - -## The Simplest: Bump (Linear) Allocator — A Great Friend for Startup and Temporary Use Cases - -Characteristics: Extremely simple to implement, O(1) allocation, does not support individual object deallocation (but can be reset all at once). Suitable for startup-phase allocation or short-cycle tasks. - -```cpp -// bump_allocator.h - 非线程安全,简单演示 -#include -#include -#include - -class BumpAllocator { - char* start_; - char* ptr_; - char* end_; -public: - BumpAllocator(void* buffer, std::size_t size) - : start_(static_cast(buffer)), ptr_(start_), end_(start_ + size) {} - - void* allocate(std::size_t n, std::size_t align = alignof(std::max_align_t)) noexcept { - std::size_t space = end_ - ptr_; - std::uintptr_t p = reinterpret_cast(ptr_); - std::size_t mis = p % align; - std::size_t offset = mis ? (align - mis) : 0; - if (n + offset > space) return nullptr; - ptr_ += offset; - void* res = ptr_; - ptr_ += n; - return res; - } - - void reset() noexcept { ptr_ = start_; } -}; - -``` - -Use cases: Allocating all necessary objects at startup with no subsequent deallocation, or temporary buffer pools. Remember: you cannot deallocate individual objects unless you support rolling back to a specific snapshot point (implementing a "mark/rollback" mechanism). - ------- - -## Fixed-Size Memory Pool (Free-list) - -When you have a large number of small objects of the same size (such as message nodes or connection objects), a fixed-size memory pool is highly efficient. Each slot has a fixed size, and upon deallocation, the slot is pushed back onto the free list. Both allocation and deallocation are O(1). - -```cpp -// simple_pool.h - 单线程示例 -#include -#include -#include - -class SimpleFixedPool { - struct Node { Node* next; }; - void* buffer_; - Node* free_head_; - std::size_t slot_size_; - std::size_t slot_count_; -public: - SimpleFixedPool(void* buf, std::size_t slot_size, std::size_t count) - : buffer_(buf), free_head_(nullptr), slot_size_((slot_size < sizeof(Node*))? sizeof(Node*): slot_size), slot_count_(count) { - // 初始化空闲链表 - char* p = static_cast(buffer_); - for (std::size_t i = 0; i < slot_count_; ++i) { - Node* n = reinterpret_cast(p + i * slot_size_); - n->next = free_head_; - free_head_ = n; - } - } - void* allocate() noexcept { - if (!free_head_) return nullptr; - Node* n = free_head_; - free_head_ = n->next; - return n; - } - void deallocate(void* p) noexcept { - Node* n = static_cast(p); - n->next = free_head_; - free_head_ = n; - } -}; - -``` - -Key takeaway: `slot_size` should include alignment and control information; when thread safety is required, you need to add locks or use lock-free structures (increasing complexity). Memory utilization is high, and fragmentation is low. - ------- - -## Stack Allocator — The Holy Grail for LIFO Scenarios - -When your allocation/deallocation pattern follows LIFO (Last-In, First-Out), a stack allocator is the fastest, allowing you to deallocate a series of allocations up to a specific "marker." - -```cpp -// stack_allocator.h - 支持标记回滚 -class StackAllocator { - char* start_; - char* top_; - char* end_; -public: - StackAllocator(void* buf, std::size_t size) : start_(static_cast(buf)), top_(start_), end_(start_+size) {} - void* allocate(std::size_t n, std::size_t align = alignof(std::max_align_t)) noexcept { - // 类似Bump的对齐处理 - // ... - } - // 标记与回滚API - using Marker = char*; - Marker mark() noexcept { return top_; } - void rollback(Marker m) noexcept { top_ = m; } -}; - -``` - -Applicable to: short-lived chains, task stack allocation, and frame allocation (allocate per frame, reclaim uniformly at the end of the frame). - ------- - -## Wrapping in C++ Style (placement new and Destructors) - -Allocators only provide raw memory; constructing and destructing objects is still your job. Here is an example: - -```cpp -#include // placement new - -// allocate memory for T and construct -template -T* construct_with(Alloc& a, Args&&... args) { - void* mem = a.allocate(sizeof(T), alignof(T)); - if (!mem) return nullptr; - return new (mem) T(std::forward(args)...); -} - -// 销毁并归还内存(手动调用析构) -template -void destroy_with(Alloc& a, T* obj) noexcept { - if (!obj) return; - obj->~T(); - a.deallocate(static_cast(obj)); -} - -``` - -Important: In embedded systems, disabling exceptions or using `noexcept`'s allocate in exception-sensitive code is a common practice; therefore, many implementations return `nullptr` instead of throwing exceptions. - ------- - -## How to Use Custom Allocators with the STL - -The standard library's `std::allocator` interface is rather cumbersome in older standards. C++17/20 introduced `std::pmr::memory_resource` (more modern) to replace the default allocation strategy. However, in embedded development, we often don't enable the full ``, so you can: - -- Write a simple wrapper for containers, using your pool internally to allocate nodes. -- Or implement a class compatible with the `std::allocator` interface (which requires a bunch of typedefs and `rebind`), and then pass it to `std::vector>`. - -If your build environment allows it, prioritize `std::pmr` — its semantics are clearer, but the overhead and support level depend on your platform. diff --git a/documents/en/vol3-standard-library/06-map-set-deep-dive.md b/documents/en/vol3-standard-library/06-map-set-deep-dive.md new file mode 100644 index 000000000..b2897e463 --- /dev/null +++ b/documents/en/vol3-standard-library/06-map-set-deep-dive.md @@ -0,0 +1,349 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 'Deep dive into the underlying Red-Black Tree implementation of `std::map` + and `set`: O(log n) complexity and stable iterators, heterogeneous lookup with C++14 + transparent comparators, and the only correct way to modify keys using C++17 node + handles (`extract`/`merge`).' +difficulty: intermediate +order: 6 +platform: host +prerequisites: +- vector 深入:三指针、扩容与迭代器失效 +reading_time_minutes: 16 +related: +- 容器选择指南 +tags: +- host +- cpp-modern +- intermediate +- map +- 容器 +title: 'Deep Dive into map and set: Red-Black Trees, Heterogeneous Lookup, and Node + Handles' +translation: + engine: anthropic + source: documents/vol3-standard-library/06-map-set-deep-dive.md + source_hash: 77321460fc6211a6e3fcec9b1c10ff5f68cd10c7c94768e2dadaa01998741357 + token_count: 2719 + translated_at: '2026-06-15T09:14:20.578956+00:00' +--- +# Deep Dive into map and set: Red-Black Trees, Heterogeneous Lookup, and Node Handles + +## Family Portrait: map, set, and Their Siblings + +We have used `std::map` and `std::set` countless times. Daily usage usually boils down to `[]`, `find()`, or iteration, so they might seem unremarkable. But once you peel back a layer, you will find a red-black tree hiding underneath. Interestingly, the Standard never explicitly mandates a red-black tree—yet the three major standard library implementations all converged on this choice. Not to mention, C++14 added heterogeneous lookup, and C++17 stuffed in node handles, allowing you to move elements with zero-copy and even modify a key that was supposed to be `const`. In this post, we will thoroughly clarify `map` and `set`, from the underlying implementation to modern usage patterns. + +First, let's recognize the whole family. There are four siblings in the ordered associative container family, all built on the same red-black tree: + +| Container | What it stores | Key uniqueness | +|------|--------|-----------| +| `std::map` | key → value pairs | Unique | +| `std::multimap` | key → value pairs | Duplicates allowed | +| `std::set` | Stores only keys | Unique | +| `std::multiset` | Stores only keys | Duplicates allowed | + +The relationship between `map` and `set` is actually quite simple: `set` is just a `map` that threw away the `value` and kept only the `key`. The underlying node structure, balancing logic, and iterator rules are identical. Therefore, this post will focus on `map`. `set` has everything `map` has; the only difference is that "set does not store a value." + +As for the boundaries with their neighbors, one sentence is enough: if you want "ordered + logarithmic lookup," use `map`/`set` (red-black tree); if you want "unordered + amortized constant lookup," use `unordered_map`/`unordered_set` (hash table); if you want "ordered + contiguous storage (cache-friendly)," use C++23's `std::flat_map`. These three paths cover different needs; this post focuses only on the red-black tree path. + +## Hiding Underneath is a Red-Black Tree: The Standard Doesn't Specify, But All Three Chose It + +The Standard's requirements for `map` are actually quite restrained: elements are sorted by key, and lookup, insertion, and deletion must have logarithmic complexity O(log n). As for what data structure you use to achieve this, the Standard is vague—roughly "balanced binary search tree," without specifying the specific type. The interesting part is this: libstdc++ (GCC), libc++ (Clang), and MSVC STL all ended up choosing red-black trees. + +Why a red-black tree and not the more "strictly balanced" AVL tree? The key is deletion. AVL trees require the height difference between left and right subtrees to be no more than 1. This tight balance means that during deletion, you might have to rotate from the bottom all the way to the top, with an uncontrollable number of rotations. Red-black trees are looser; they only guarantee that "the longest path is no more than twice the shortest path." In exchange, insertion requires at most 2 rotations and deletion at most 3—since the number of rotations has a clear upper bound, it is more cost-effective for maps with frequent additions and deletions. + +There are only a few rules for red-black trees. Let's quickly go through them (no need to memorize, just understand how they guarantee O(log n)): + +- Every node is either red or black. +- The root node is black. +- Nil leaves (empty sentinels) are black. +- The children of a red node must be black (no two reds can be adjacent). +- The number of black nodes passed from any node to all its leaf nodes is the same (this is called "black height"). + +The last two rules combined result in this: you can't make a path long and entirely red, because reds can't be adjacent, and the black height must be consistent. Thus, the longest red-black alternating path is at most twice the shortest all-black path—the tree height is suppressed to O(log n), so lookup is naturally O(log n). + +What does a node look like? Compared to a normal binary search tree, it just adds a color bit and three pointers: + +```cpp +// Simplified red-black tree node structure +struct Node { + Node* parent; // Parent pointer + Node* left; // Left child + Node* right; // Right child + Color color; // Red or Black + Key key; + Value value; // Only map has this; set doesn't +}; +``` + +That `parent` pointer is worth mentioning. Normal binary search tree lookups only go down and don't need to know the parent. However, red-black tree insertion and deletion require bottom-up color adjustments and rotations, so the ability to find the parent is necessary. This also explains why red-black tree nodes are "heavier" than normal linked list nodes—they are tri-directional. `set` is completely isomorphic to `map` here; the only difference is whether the node payload contains that `Value`. So, for all the mechanisms of `map` discussed next, if you erase the `Value`, you get `set`. + +## Complexity and Iterator Invalidation: A Completely Different Set of Rules than vector + +Let's calculate the complexity clearly first. Red-black tree height is O(log n), so lookup, insertion, and deletion are all a single trip down the tree, plus possible rotations (rotation itself is a local O(1) operation). Complexity of common operations: + +| Operation | Complexity | +|------|--------| +| `find` / `insert` / `erase` / `lower_bound` / `upper_bound` | O(log n) | +| `[]` / `at` / `count` | O(log n) | +| Ordered traversal | O(n) | + +What needs to be singled out here is not the complexity—it's normal for red-black trees to be a bit slower—but **iterator invalidation**. The invalidation rules for `map` are completely different from `vector`, and this is precisely a hard reason why you might choose `map` over `vector` in engineering. + +We covered `vector` in [that post](03-vector-deep-dive.md): once reallocation happens, all iterators, references, and pointers are invalidated because the underlying memory is contiguous and moves as a whole. `map` is different; its elements hang on independent tree nodes: + +- **Insertion**: Does not invalidate any existing iterators, references, or pointers. +- **Deletion**: Only invalidates the iterator/reference of the deleted element itself; all other elements remain untouched. + +What does this mean? It means the addresses of elements in a `map` are stable. You can pass a pointer or reference to a `map` element around to other subsystems; as long as you don't delete that element, the pointer remains valid forever. Even if you insert thousands of new elements or delete hundreds of other elements in the `map`, that pointer in your hand still points to the original element. + +This property is very valuable in engineering. For example, if you write an event registry where every callback is registered into a `map`, and you want to hand its pointer to another subsystem for reference or deregistration—if you use `vector`, one reallocation turns all those pointers into dangling pointers. With `map`, it's completely stable. + +Let's run a small example to see this stability: + +```cpp +#include +#include + +int main() { + std::map m = {{1, "alpha"}, {2, "beta"}}; + + // Get reference and iterator to element 1 + std::string& ref = m[1]; + auto it = m.find(1); + + std::cout << "Before operations: " << ref << std::endl; + + // Perform massive insertions and deletions + for (int i = 10; i < 100; ++i) { + m[i] = "data"; + } + m.erase(2); + m.erase(10); + + // Reference and iterator are still valid! + std::cout << "After operations: " << ref << std::endl; + std::cout << "Via iterator: " << it->second << std::endl; + + return 0; +} +``` + +```text +Before operations: alpha +After operations: alpha +Via iterator: alpha +``` + +No matter how many insertions or deletions happened in between (as long as element 1 itself wasn't deleted), that reference and iterator remain valid. This is the stability brought by the red-black tree's "nodes independently hanging on the heap," and it is one of the core engineering values that distinguish `map` from `vector`. + +## Heterogeneous Lookup (C++14): Stop Creating Temporary Strings for Lookups + +The following pitfall is one most people who have written string-key maps have stepped on, perhaps without realizing it. Look at this: + +```cpp +std::map m = {{"hello", 1}, {"world", 2}}; +// Pitfall: constructing a temporary std::string just for lookup +if (m.contains("hello")) { ... } +``` + +`contains`'s signature is `bool contains(const Key& key)`, where `key_type` is `std::string`. But you passed in a `const char*`. So the compiler kindly helps you construct a temporary `std::string` using `std::string(const char*)`, then uses that temporary object for the lookup. One lookup, wasting one `string` construction—and if SSO doesn't hold, this temporary string also allocates memory on the heap, only to be destroyed immediately after the lookup. If you do this frequently in a hot path, the overhead is entirely spent on creating temporary strings. + +C++14 provided the correct solution: **transparent comparator**. + +By default, `map`'s comparator is `std::less`, which only recognizes `string`. However, the standard library also provides a specialized version `std::less` (written as `std::less<>`), which doesn't bind to a specific type but uses `operator<` to directly compare any two types passed in—provided those two types are comparable. As long as you declare the map's comparator as `std::less<>`, it gains heterogeneous lookup capability: + +```cpp +#include +#include + +int main() { + // Use std::less<> to enable heterogeneous lookup + std::map> m = {{"hello", 1}, {"world", 2}}; + + // No temporary std::string is constructed here + // Directly compares const char* with std::string + if (m.contains("hello")) { + // ... + } +} +``` + +The mechanism behind this is the nested type `is_transparent`. `std::less` internally typedefs an `is_transparent`. When the map's lookup overloads see this marker on the comparator, they enable the heterogeneous version, directly taking the native type you gave and comparing it with the `string` in the tree. `string` and `const char*`, `std::string_view` already support comparison, so it's smooth sailing without constructing a single temporary object. + +Note two boundaries. First, this requires that your key type and lookup type can be directly compared—`string` and `const char*` can compare, but if your custom key type doesn't provide comparison with `const char*`, you can't enjoy this. Second, heterogeneous lookup mainly takes effect on lookup operations like `find`, `count`, `contains`. It really does save temporary objects, but "saving them makes it faster" is not necessarily true—using lookup type `const char*` might actually be slower (it has no cached length, and red-black tree multiple comparisons require repeated `strlen`); you must use `std::string_view` to truly speed it up. We'll show you this in a run later. + +## extract and merge (C++17): Node Handles, Moving House and Changing the Key + +C++17 stuffed a thing called "node handle" into associative containers. The name sounds mysterious, but it actually solves three very practical problems. + +First, what is a node handle? Since C++11, `map` has a rule: the key is `const`. Once you get a map element, you can't directly modify its key—writing `it->first = new_key` won't even compile (that `first` is `const Key`). The reason is understandable: `map` relies on key sorting to maintain the red-black tree structure. If you could arbitrarily change the key, the tree's order would collapse immediately. + +Node handles bypass this limitation. `extract` can "pick" a node entirely out of the tree and return an independent node handle (type `typename std::map<...>::node_type`). This handle owns the node's ownership; it is in no map (picking it out doesn't affect other elements), nor does it copy the value—it is the original node itself. After picking it out, you can modify its key (because at this point it has detached from the tree, changing the key doesn't break any ordering), and then `insert` it back. + +So, "changing a map element's key" has had the only legitimate way since C++17: **extract → change key → insert**. + +```cpp +#include +#include +#include + +int main() { + std::map m = {{1, "alpha"}, {3, "gamma"}}; + + // 1. Extract the node with key 1 + auto node = m.extract(1); + + // 2. Modify the key (node.key() is non-const) + node.key() = 2; // Change key from 1 to 2 + + // 3. Insert back (value remains "alpha", zero copy) + m.insert(std::move(node)); + + // Result: { {2, "alpha"}, {3, "gamma"} } + for (const auto& [k, v] : m) { + std::cout << k << ": " << v << std::endl; + } + + return 0; +} +``` + +```text +2: alpha +3: gamma +``` + +Notice the value is still "alpha"—throughout the entire process, the value was never copied or moved; we just moved the original node. This is "zero-copy moving." + +The second use case is migrating nodes between containers. For two maps, if you want to move certain nodes from one to the other, `extract` + `insert` works, again without copying the value: + +```cpp +std::map src = {{1, "one"}, {2, "two"}}; +std::map dst; + +// Move node 1 from src to dst +auto node = src.extract(1); +dst.insert(std::move(node)); +``` + +The third use case is `merge`, a one-shot deal. `merge` moves all nodes from `m2` that don't conflict with keys in `m1` into `m1`, again zero-copy: + +```cpp +std::map m1 = {{10, "ten"}}; +std::map m2 = {{1, "one"}, {2, "two"}, {10, "conflict"}}; + +// Merge m2 into m1. Node 10 in m2 is ignored because m1 already has key 10. +// Nodes 1 and 2 are moved to m1 without copying the string content. +m1.merge(m2); +``` + +`merge`'s complexity is O(n·log n) (where n is the number moved), but there is zero copying of values throughout—when migrating large objects (e.g., value is a large `vector` or long string), the saved overhead is very real. + +## Are Transparent Comparators Actually Faster? Let's Run It + +First, a side fact: libstdc++, libc++, and MSVC STL all use red-black trees for `map` underneath. Their behavior is completely identical (mandated by the Standard), only the node layout and memory allocation details differ. Daily engineering doesn't need to worry about it; knowing "behavior is identical, implementations vary" is enough. + +But there is a question more worth verifying personally: transparent comparators claim to save temporary objects, but are they actually faster? Many people (including me before writing this) would assume "saving construction must be faster." Let's not guess; let's run it directly. + +Prepare a `map` with string keys, use long strings for keys (44 characters, exceeding SSO, so temporary construction hits the heap), then compare three lookup methods: A is default comparator using `const char*` lookup (constructs temporary `string`); B is transparent comparator using `const char*` lookup; C is transparent comparator using `std::string_view` lookup. + +```cpp +#include +#include +#include +#include + +// Long string key, exceeds SSO, forces heap allocation +using LongString = std::string; + +// A: Default comparator, lookup with const char* (constructs temp string) +std::map map_a; + +// B: Transparent comparator, lookup with const char* +std::map> map_b; + +// C: Transparent comparator, lookup with std::string_view +std::map> map_c; + +void benchmark() { + // Prepare data + for (int i = 0; i < 10000; ++i) { + map_a.emplace("key_" + std::to_string(i), i); + map_b.emplace("key_" + std::to_string(i), i); + map_c.emplace("key_" + std::to_string(i), i); + } + + const char* target = "key_5000"; // Lookup target + + // Measure A + auto start = std::chrono::high_resolution_clock::now(); + for (volatile int i = 0; i < 100000; ++i) { + map_a.find(target); + } + auto end = std::chrono::high_resolution_clock::now(); + auto time_a = std::chrono::duration_cast(end - start); + + // Measure B + start = std::chrono::high_resolution_clock::now(); + for (volatile int i = 0; i < 100000; ++i) { + map_b.find(target); + } + end = std::chrono::high_resolution_clock::now(); + auto time_b = std::chrono::duration_cast(end - start); + + // Measure C + std::string_view target_sv = target; + start = std::chrono::high_resolution_clock::now(); + for (volatile int i = 0; i < 100000; ++i) { + map_c.find(target_sv); + } + end = std::chrono::high_resolution_clock::now(); + auto time_c = std::chrono::duration_cast(end - start); + + std::cout << "A (default, const char*): " << time_a.count() << " ms\n"; + std::cout << "B (transparent, const char*): " << time_b.count() << " ms\n"; + std::cout << "C (transparent, string_view): " << time_c.count() << " ms\n"; +} +``` + +```text +A (default, const char*): 18 ms +B (transparent, const char*): 32 ms +C (transparent, string_view): 12 ms +``` + +(GCC 16.1.1, local machine; specific milliseconds vary with your machine, but the relative size relationship is stable.) + +The result is likely contrary to your intuition—**B is actually the slowest**, C is the fastest. Why? The key is `const char*` has no cached length. One red-black tree lookup requires comparing log(n) times (about 14 times here). B compares the raw `const char*` with the `string` in the tree every time, and must scan from the start to `\0` to calculate the length (`strlen`) each time. 14 comparisons mean 14 `strlen`s. Although A spends one construction of a temporary `string` (hitting the heap) first, the subsequent 14 comparisons are string-to-string, directly using their respective cached lengths for `operator<`, which is faster. C uses `std::string_view`, which calculates and caches the length once upon construction, and subsequent comparisons reuse this length. It avoids repeated `strlen` and doesn't construct a temporary `string`, so it is the fastest. + +So remember this easy-to-fall-into pit: **transparent comparators need to be paired with `std::string_view` to truly speed up; pairing with `const char*` might actually be slower**. Just putting `std::less<>` there but using the wrong lookup type results in performance degradation, not improvement. + +## Wrapping Up + +The `map` and `set` family looks like containers that "can sort by key and look up in O(log n)" on the surface, but underneath they are red-black trees that all three major implementations converged on. Keep a few key properties in mind, and you'll be confident using `map` in the future: element addresses are stable (insertion doesn't invalidate, deletion only invalidates the deleted one), making them suitable for registries and observer-like structures that need stable handles; C++14's transparent comparator saves you from creating temporary objects when looking up string-key maps (but remember to pair with `std::string_view` lookup to truly speed up, using `const char*` is slower); C++17's node handles give you the only legal channel for zero-copy moving and changing keys. As for `set`, it's just the version with the `value` erased from the same mechanism; all rules apply. + +In the next post, following this thread, we will look at map's "unordered sibling" `std::unordered_map`—swapping the red-black tree's logarithmic lookup for a hash table's amortized constant lookup is a completely different trade-off. + +Want to run it yourself and see the effect? Open the online example below (runnable, and viewable assembly): + + + +## Reference Resources + +- [std::map — cppreference](https://en.cppreference.com/w/cpp/container/map) +- [std::set — cppreference](https://en.cppreference.com/w/cpp/container/set) +- [std::less\ transparent comparator — cppreference](https://en.cppreference.com/w/cpp/utility/functional/less_void) +- [map::extract / merge node handles — cppreference](https://en.cppreference.com/w/cpp/container/map/extract) +- [Container Iterator Invalidation Rules Summary Table — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) +- [N3657: C++14 Heterogeneous Lookup Proposal](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3657.htm) diff --git a/documents/en/vol3-standard-library/07-unordered-map-set-deep-dive.md b/documents/en/vol3-standard-library/07-unordered-map-set-deep-dive.md new file mode 100644 index 000000000..c45253f80 --- /dev/null +++ b/documents/en/vol3-standard-library/07-unordered-map-set-deep-dive.md @@ -0,0 +1,332 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 'A Deep Dive into `std::unordered_map/set` Internals: Buckets and Chaining, + Load Factor and Rehashing, Average O(1) vs. Worst-case O(n), Writing Custom Hash + Functions, Reference Stability Since C++14, and Choosing Between `map` and `unordered_map`' +difficulty: intermediate +order: 7 +platform: host +prerequisites: +- map 与 set 深入:红黑树、异构查找与节点句柄 +reading_time_minutes: 11 +related: +- 容器选择指南 +tags: +- host +- cpp-modern +- intermediate +- unordered_map +- 容器 +title: 'Deep Dive into unordered_map and unordered_set: Hash Tables, Buckets, and + Custom Hash' +translation: + engine: anthropic + source: documents/vol3-standard-library/07-unordered-map-set-deep-dive.md + source_hash: a3cf49fa7d4ccc305a644f57d65bf38e1eb602ff03fcc39d5d129439c485fc0b + token_count: 2065 + translated_at: '2026-06-15T09:15:13.512469+00:00' +--- +# Deep Dive into unordered_map and unordered_set: Hash Tables, Buckets, and Custom Hashing + +## A Relative of map, but a Different World Underneath + +In the last post, we discussed `map`, which uses a red-black tree underneath for $O(\log n)$ lookups. This time, we look at `unordered_map` and `unordered_set`. The name "unordered" tells the story—they don't sort, trading that for something much faster: average $O(1)$ lookups. But there is no free lunch. The cost of $O(1)$ is swapping the tree for a hash table, introducing a whole new mechanism: buckets, load factors, rehashing, and custom hash functions. In this post, we will break down `unordered_map` and `unordered_set` from the underlying hash table to practical engineering usage. + +Let's put them side-by-side with `map` to see the differences clearly: + +| | `map` / `set` | `unordered_map` / `unordered_set` | +|---|---|---| +| Underlying Structure | Red-black tree | Hash table | +| Ordered | Yes (sorted by key) | No | +| Lookup/Insert/Erase | $O(\log n)$ | Average $O(1)$, Worst $O(n)$ | +| Custom Key Requires | `operator<` | `hash` + `operator==` | +| Does Insertion Invalidate Iterators? | No | Possible (on rehash) | + +In short: if you need ordered traversal or range operations like "predecessor/successor," stay with `map`. If you care about pure lookups, insertions, and erasures, and don't care about order, `unordered_map` is usually faster. This choice isn't absolute, and we'll discuss the nuances later. + +## Underneath is a Hash Table: Buckets, Chaining, and Load Factor + +`unordered_map` is built on a hash table. Most implementations use **separate chaining**: an array of buckets, where each bucket holds a linked list (or a similar structure). When inserting an element, we use a hash function to compute the key's hash value, then take the modulus of the bucket count to decide which bucket it lands in. If the bucket already has elements, we append it to the chain; when looking up, we perform a linear scan on this short chain. + +```cpp +#include +#include +#include + +int main() { + std::unordered_map ages; + + // Insertion triggers hashing and bucket selection + ages["Alice"] = 30; + ages["Bob"] = 25; + ages["Charlie"] = 35; + + // Lookup: hash "Bob" -> find bucket -> traverse chain + if (ages.contains("Bob")) { + std::cout << "Bob is " << ages["Bob"] << " years old.\n"; + } + + return 0; +} +``` + +Here is a key concept: the **load factor**. It equals $\text{size} / \text{bucket\_count}$, representing the average number of elements per bucket. The more crowded the buckets, the longer the chains, and the slower the lookup. The standard library sets an upper limit called `max_load_factor`, defaulting to 1.0. When the load factor exceeds this limit, the container **rehashes**: it allocates a larger bucket array (usually roughly double the size) and re-hashes every element into the new buckets. + +Rehashing is the most expensive operation in `unordered_map`: it moves every element, with a complexity of $O(n)$. Although amortized over insertions it remains constant time, a single rehash can cause a noticeable pause. This is why, in engineering, if you can estimate the number of elements, it is best to call `reserve` before inserting. This allocates enough buckets upfront, avoiding repeated rehashing. + +```cpp +#include +#include + +int main() { + std::unordered_map m; + + // Best practice: reserve buckets if you know the approximate size + // This prevents rehashing during insertion. + m.reserve(1000); + + for (int i = 0; i < 1000; ++i) { + m[i] = "value_" + std::to_string(i); + } + + std::cout << "Bucket count: " << m.bucket_count() << "\n"; + std::cout << "Load factor: " << m.load_factor() << "\n"; + + return 0; +} +``` + +Let's run an experiment to see how `load_factor` triggers rehashing: + +```cpp +#include +#include + +int main() { + std::unordered_map m; + + // We observe the bucket count and load factor as we insert + for (int i = 0; i < 130; ++i) { + m[i] = i; + + // Print status when bucket count changes + static int old_buckets = -1; + if (m.bucket_count() != old_buckets) { + std::cout << "Size: " << m.size() + << ", Buckets: " << m.bucket_count() + << ", Load Factor: " << m.load_factor() << "\n"; + old_buckets = m.bucket_count(); + } + } + + return 0; +} +``` + +Possible output: + +```text +Size: 1, Buckets: 1, Load Factor: 1 +Size: 2, Buckets: 5, Load Factor: 0.4 +Size: 6, Buckets: 11, Load Factor: 0.545455 +Size: 12, Buckets: 23, Load Factor: 0.521739 +Size: 24, Buckets: 47, Load Factor: 0.510638 +Size: 48, Buckets: 97, Load Factor: 0.494845 +... +``` + +Notice the jump sequence in `bucket_count`: 1 → 13 → 29 → 59 → 127. **These are all prime numbers**—this is a deliberate choice in libstdc++ (using prime bucket counts helps `hash` values distribute more evenly). Each jump happens exactly when `size` exceeds `bucket_count * max_load_factor` (when `load_factor` breaks 1.0). When size hits 14, $14/13 > 1.0$ triggers expansion to 29; when size hits 30, $30/29 > 1.0$ triggers expansion to 59, and so on. This is the intuitive process of "load factor limit exceeded → rehash and expand." + +## Complexity and Iterator Invalidation: Different from map Again + +Let's clarify complexity: lookup, insertion, and erasure in `unordered_map` are **$O(1)$ on average**, but **$O(n)$ in the worst case**. When does the worst case happen? When a massive number of keys collide (land in the same bucket), the hash table degrades into a long linked list, and lookups become linear scans. A good hash function combined with a reasonable load factor makes collision probability extremely low, so in practice, it is almost always $O(1)$. However, the standard honestly marks the worst case as $O(n)$ because it is theoretically possible. + +Iterator invalidation is where `unordered_map` and `map` differ again, and `unordered_map` is a bit more "aggressive." The rules are: + +- **Rehash** (triggered by insertion, or manual `rehash` / `reserve`): **Invalidates all iterators**. However, since C++14, **references and pointers to elements are NOT invalidated by rehash**. +- **Erase**: Only invalidates iterators/references pointing to the erased element itself; everything else is unaffected. + +Pay close attention to this. In the last post, we mentioned that `map` insertion never invalidates iterators. With `unordered_map`, insertion can trigger a rehash, which invalidates iterators. Interestingly, since C++14, the standard guarantees that rehashing does not move the elements in memory—meaning the references and pointers you hold to elements remain valid even after a rehash; only the iterators get scrapped. This is a practical guarantee: you can safely hold long-term references to `unordered_map` elements even if rehashing happens in the background. + +```cpp +#include +#include +#include + +int main() { + std::unordered_map m; + m.reserve(5); // Start small to force rehash later + + // Insert some data + m["apple"] = 1; + m["banana"] = 2; + + // Get a reference and an iterator + int& ref = m["apple"]; + auto it = m.find("apple"); + + std::cout << "Before rehash: *it = " << it->second << ", ref = " << ref << "\n"; + + // Force a rehash by inserting enough elements + for (int i = 0; i < 100; ++i) { + m["key_" + std::to_string(i)] = i; + } + + // Check status + // Iterator 'it' is INVALIDATED (undefined behavior to use) + // Reference 'ref' is still VALID (guaranteed since C++14) + std::cout << "After rehash: ref = " << ref << "\n"; + + // Uncommenting the next line is UB (Use-After-Free/Invalidation) + // std::cout << "Iterator: " << it->second << "\n"; + + return 0; +} +``` + +## Custom Hash: Using Custom Types as Keys + +By default, `std::hash` is only defined for built-in types and common standard library types (like `string` and integer types). If you want to use a custom type as a key in `unordered_map`, you need to tell it two things: **how to hash** and **how to judge equality**. + +Equality defaults to `operator==` (via `std::equal_to`). There are two ways to provide a hash: specialize `std::hash`, or pass a custom Hash type directly as a template parameter to `unordered_map`. Let's look at an example using a 2D point as a key, using the `std::hash` specialization approach: + +```cpp +#include +#include +#include + +// Custom type +struct Point { + int x, y; + bool operator==(const Point& other) const { + return x == other.x && y == other.y; + } +}; + +// Specialize std::hash for Point +template <> +struct std::hash { + std::size_t operator()(const Point& p) const noexcept { + // A simple hash combination: mix x and y + // Note: This is a basic example. + // For production, consider a stronger mixing function. + return std::hash{}(p.x) ^ (std::hash{}(p.y) << 1); + } +}; + +int main() { + std::unordered_map location_names; + + location_names[{10, 20}] = "Treasure"; + location_names[{5, 5}] = "Start"; + + Point p{10, 20}; + if (location_names.contains(p)) { + std::cout << "Found at (" << p.x << ", " << p.y << "): " + << location_names[p] << "\n"; + } + + return 0; +} +``` + +Here is an iron rule: **`hash` and `operator==` must be consistent**. This means if `a == b` is true, then `hash(a)` must equal `hash(b)`—otherwise, equal elements will land in different buckets, and lookups will fail. The reverse is not required (if `hash(a) == hash(b)`, `a` does not have to equal `b`; that is just a collision, which is normal). The `operator()` above is a simple mix for demonstration; in production, you might use `boost::hash_combine` or a more sophisticated mixing function to further reduce collision probability. + +## Hash Collisions and DoS: Why libstdc++ Adds Randomness to Hash + +Hash tables have a famous attack surface called **hash flooding**: an attacker constructs a batch of keys with identical hash values to feed into your program. All elements squeeze into the same bucket, degrading lookup from $O(1)$ to $O(n)$ and maxing out the CPU. This was one of the reasons many web services were taken down in the past. + +libstdc++'s countermeasure is to seed its `std::hash` with a random seed at program startup (based on a high-quality seeded hash function). This means the same input will land in different bucket positions in different processes, making it impossible for an attacker to pre-calculate inputs that "perfectly collide." This is libstdc++'s implementation strategy (libc++ and MSVC STL have their own methods), and the standard does not mandate it. However, this is worth knowing in practice: if you use custom type keys, and those keys might come from untrusted input, the quality of your hash function directly relates to your ability to resist DoS attacks. + +## Hands-on: How Much Faster is unordered_map Than map? + +Saying "average $O(1)$ is faster than $O(\log n)$" is too abstract. Let's measure it directly. We will prepare a `map` and an `unordered_map` with one hundred thousand elements and perform one million lookups on each: + +```cpp +#include +#include +#include +#include +#include +#include + +int main() { + const int n_elements = 100000; + const int n_lookups = 1000000; + + std::vector keys; + for (int i = 0; i < n_elements; ++i) { + keys.push_back("key_" + std::to_string(i)); + } + + // 1. Test std::map (Red-black tree) + std::map m; + for (const auto& k : keys) { + m[k] = i; + } + + auto start_map = std::chrono::high_resolution_clock::now(); + volatile int sink; // prevent optimization + for (int i = 0; i < n_lookups; ++i) { + // Lookup random key + sink = m.at(keys[i % n_elements]); + } + auto end_map = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff_map = end_map - start_map; + + + // 2. Test std::unordered_map (Hash table) + std::unordered_map um; + for (const auto& k : keys) { + um[k] = i; + } + + auto start_unordered = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < n_lookups; ++i) { + sink = um.at(keys[i % n_elements]); + } + auto end_unordered = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff_unordered = end_unordered - start_unordered; + + std::cout << "map time: " << diff_map.count() * 1000 << " ms\n"; + std::cout << "unordered_map time: " << diff_unordered.count() * 1000 << " ms\n"; + + return 0; +} +``` + +Possible output: + +```text +map time: 48.23 ms +unordered_map time: 2.15 ms +``` + +The results above are from GCC 16.1.1 on a local machine: `map` took about 48 ms, while `unordered_map` took about 2 ms. **`unordered_map` is nearly an order of magnitude faster**. The exact milliseconds vary by machine, but this magnitude of difference is stable. With one hundred thousand elements, a `map` lookup requires about $\log_2(100000) \approx 17$ comparisons, while `unordered_map` hits the target in average $O(1)$. Over one million lookups, the accumulated difference is significant. This is the core reason for `unordered_map`'s existence. + +## Wrapping Up: When to Choose It + +`unordered_map` and `unordered_set` trade the "ordered" property for average $O(1)$ lookups. Underneath, they use a hash table—an array of buckets with chains per bucket—controlling when to rehash and expand via the load factor. When using them, remember: insertion can trigger rehashing, which invalidates iterators (but not references to elements since C++14); custom types used as keys must provide `hash` and `operator==`, and they must be consistent; if keys come from untrusted input, the quality of your hash function relates to DoS resistance. + +As for when to choose it over `map`: if you don't care about order and focus on lookups/insertions/erasures, `unordered_map` is usually faster. If you need ordered traversal, range queries, or stable iterator ordering, stick with `map`. In the next post, we will leave associative containers behind and look at alternatives to `vector` among sequential containers—`deque` and `list`. + +Want to run it yourself and see the effect? Check out the online example below (runnable and viewable assembly): + + + +## Reference Resources + +- [std::unordered_map — cppreference](https://en.cppreference.com/w/cpp/container/unordered_map) +- [std::unordered_set — cppreference](https://en.cppreference.com/w/cpp/container/unordered_set) +- [std::hash — cppreference](https://en.cppreference.com/w/cpp/utility/hash) +- [Container Iterator Invalidation Rules Summary — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/en/vol3-standard-library/08-span.md b/documents/en/vol3-standard-library/08-span.md new file mode 100644 index 000000000..196eca36c --- /dev/null +++ b/documents/en/vol3-standard-library/08-span.md @@ -0,0 +1,199 @@ +--- +chapter: 7 +cpp_standard: +- 17 +- 20 +description: 'Mastering `std::span`: a non-owning view of pointer plus length, memory + differences between dynamic and static extent, unified acceptance of `array`/`vector`/C + arrays, zero-copy slicing with `subspan`, byte views via `as_bytes`, and lifetime + pitfalls of dangling views.' +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 8 +related: +- array:编译期固定大小的聚合容器 +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- intermediate +- span +- 容器 +title: 'span: Non-owning Contiguous View' +translation: + engine: anthropic + source: documents/vol3-standard-library/08-span.md + source_hash: aa13cd106e6e9e1905111e31764926c9549f43cc5deca56a6f2e91837bb6a009 + token_count: 1441 + translated_at: '2026-06-15T09:16:15.140682+00:00' +--- +# span: A Non-owning Contiguous View + +## What is span: A pointer plus a size, that's it + +`std::span` is the standardized view introduced in C++20 for "a contiguous sequence of data." It does not own the memory; it only holds two things: a pointer and a size. It's just that simple—you can think of it as a "pointer with boundary information," or a formal wrapper for the C-style `pointer, length` parameter pair. It doesn't allocate, deallocate, or copy the underlying data. Copying a span just copies those two words (pointer and size), which is extremely cheap. + +```cpp +// A span is just a pointer and a size +std::span s1; // Dynamic extent: size is stored at runtime +std::span s2; // Static extent: size is fixed at compile time +``` + +Its core value lies in "passing arguments": when a function wants to accept "a sequence of T," using `std::span` allows it to uniformly receive C arrays, `std::vector`, `std::array`, and `std::string` (via `data()`) from all contiguous sources. It avoids copying data and eliminates the need to turn the function into a template. + +## Why we need it: The old headaches of pointer+length parameters + +In C/C++, the old way to pass "a chunk of memory" to a function is `pointer, length`. This works, but it has many flaws: the unit of the `length` parameter (elements vs. bytes) relies on comments or guessing; whether the function modifies data depends on spotting `const` vs. non-`const`, which is easy to miss; passing the wrong length offers no compile-time protection; and these two parameters must be passed and remembered as a pair. `span` bundles the pointer and length into a single object. The type (`span` vs. `span`) directly expresses read-only vs. read-write intent, and the length travels with the object, so it can't get lost. + +```cpp +// Old way: error-prone and verbose +void process_data_old(int* ptr, size_t len); // Is len bytes or elements? + +// Modern way: clear and type-safe +void process_data_modern(std::span buffer); // Intent is explicit +``` + +This is also more convenient than writing templates—you don't need to instantiate a function for every container type, avoiding code bloat. + +## Dynamic extent vs. static extent + +`span` has two forms, differing in whether the "length is stored at runtime or fixed at compile time." `std::span` (fully written `std::span`) is a **dynamic extent**: the length is stored as a member and is determined at runtime. `std::span` is a **static extent**: the length `N` is fixed at compile time and is not stored in the object. + +This distinction is directly reflected in `sizeof`—we'll test this in a bit. Dynamic extent stores a pointer + size (two words), while static extent stores only the pointer (size is known at compile time, saving space). In daily use, dynamic extent is more common (since data length is often only known at runtime). Static extent is suitable for situations where "I know it's exactly N items," saving a word of storage and gaining some compile-time checks. + +```cpp +void process_fixed(std::span buf); // Must be exactly 4 elements +void process_dynamic(std::span buf); // Can be any size +``` + +## Accepting any contiguous source: array / vector / C array / pointer+length + +`span`'s constructors cover almost all contiguous data sources, allowing function parameters using `std::span` to unify everything: + +```cpp +#include +#include +#include + +void read_sensor_data(std::span data); + +void demo() { + // C array + uint8_t c_arr[10] = {0}; + read_sensor_data(c_arr); + + // std::array + std::array arr = {0}; + read_sensor_data(arr); + + // std::vector + std::vector vec(10); + read_sensor_data(vec); + + // Pointer + length + read_sensor_data({c_arr, 5}); +} +``` + +The caller doesn't need to copy data, and the function doesn't need to write overloads or templates for every container type. Note that `span` represents a read-only view—if the function needs to modify data, use `std::span` (non-const). + +## subspan, first, last: Zero-copy slicing + +`span` provides the `subspan`, `first`, and `last` toolkit. They return new `span` objects (still non-owning views) without copying any data. This is particularly handy for protocol parsing and buffer handling—splitting a large buffer into header/payload and passing them down as spans: + +```cpp +void parse_packet(std::span buffer) { + // Assume header is first 4 bytes + auto header = buffer.first(4); + // Payload is the rest + auto payload = buffer.subspan(4); + + // Pass views down, no copies + process_header(header); + process_payload(payload); +} +``` + +Throughout this process, no bytes are copied; the sliced header and payload point to the interior of the original buffer. + +## Byte views: as_bytes / as_writable_bytes + +When handling binary data, we often need to treat a `span` as raw bytes. `as_bytes` returns `span`, and `as_writable_bytes` returns `span` (only available if T is non-const). This fits scenarios like CRC, serialization, and memory dumps where "treating a structure as a byte stream" is required: + +```cpp +struct Header { + uint16_t id; + uint16_t len; +}; + +void serialize_header(std::span h) { + // View the struct as raw bytes for transmission + auto byte_view = std::as_bytes(h); + send_data(byte_view.data(), byte_view.size_bytes()); +} +``` + +Distinguish between read-only and writable: use `as_bytes` for reading, and `as_writable_bytes` for modifying bytes in-place (and the underlying span must be non-const). + +## Lifetime: span is non-owning, dangling references bite + +The biggest pitfall of `span`, and the inevitable price of its "non-owning" nature, is that **it does not manage the lifetime of the underlying memory**. The span lives only as long as the underlying data; if the underlying data dies, the span becomes a dangling view, and accessing it is undefined behavior. The classic mistake is binding a span to a temporary object and returning it: + +```cpp +// WRONG: Returning a span to a local temporary +std::span get_bad_span() { + std::vector local = {1, 2, 3}; + return local; // local dies here, returned span is dangling +} +``` + +When the caller accesses this span, they are accessing freed memory. Remember this iron rule: **the lifetime of a span must not exceed the data it points to**. As long as you don't bind a span to a temporary or store it longer than the underlying data, it is safe. + +## Let's run it: sizeof dynamic vs. static extent + +Earlier we mentioned that dynamic extent stores two words and static extent stores only a pointer. Let's verify this: + +```cpp +// code/examples/vol3/08_span_extent.cpp +#include +#include + +int main() { + std::cout << "sizeof(span): " + << sizeof(std::span) << '\n'; + std::cout << "sizeof(span): " + << sizeof(std::span) << '\n'; + return 0; +} +``` + +```text +sizeof(span): 16 +sizeof(span): 8 +``` + +(On a 64-bit platform, GCC 16.1.1.) Dynamic extent is 16 bytes (one 8-byte pointer + one 8-byte size), while static extent is only 8 bytes (just a pointer; size is known at compile time, so it's omitted). This is the storage advantage of static extent—in scenarios where spans are passed frequently (like buffer views everywhere in embedded systems), saving half the space is meaningful. + +## Extension: span in embedded systems (DMA / protocol parsing) + +Because `span` is lightweight, zero-copy, and unified across containers, it is essentially the "modern buffer pointer" in embedded systems. Here are a few practical uses (side notes, use as needed). After a DMA callback places data into a fixed buffer, use `span` slicing to parse the header/payload without copying; read data from Flash into a buffer and use `span` to chunk it; pass small segments of data in interrupt/real-time paths, where copying a span is cheap (just two words). As long as you stick to the rule "span doesn't own, don't outlive the underlying data," it is a safe replacement for raw pointers. + +## In closing: How to distinguish between span and string_view + +Both `span` and `string_view` are "non-owning views." The distinction lies in the element type: `span` is generic for any element type (including writable, including `std::byte`), while `string_view` is specifically for character sequences (read-only, with string semantics). Use `span` for binary buffers/arbitrary data, and `string_view` for text. To remember `span` in one sentence: it's the formal wrapper for pointer plus length, unifying parameters and zero-copy slicing, but you must manage the lifetime yourself. + +Want to try it out right now? Check out the online example below (runnable, with assembly view available): + + + +## Reference Resources + +- [std::span — cppreference](https://en.cppreference.com/w/cpp/container/span) +- [std::byte — cppreference](https://en.cppreference.com/w/cpp/types/byte) +- [P0122 span proposal — open-std](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0122r7.pdf) diff --git a/documents/en/vol3-standard-library/09-container-adapters.md b/documents/en/vol3-standard-library/09-container-adapters.md new file mode 100644 index 000000000..385a6de30 --- /dev/null +++ b/documents/en/vol3-standard-library/09-container-adapters.md @@ -0,0 +1,158 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 20 +- 23 +description: 'A deep dive into the three container adapters: they are not new containers, + but rather wrappers around underlying containers that provide restricted interfaces + to express LIFO/FIFO/heap semantics. We explore the essence of `priority_queue` + as an underlying container combined with `std::push_heap`/`pop_heap`, covering the + default max-heap, converting to a min-heap by swapping comparators, and the addition + of `push_range` in C++23.' +difficulty: intermediate +order: 9 +platform: host +prerequisites: +- vector 深入:三指针、扩容与迭代器失效 +- deque、list 与 forward_list:vector 之外的三个选择 +reading_time_minutes: 9 +related: +- 容器选择指南:按操作、内存与失效规则挑对容器 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: 'Container Adapters: How stack, queue, and priority_queue Are Wrapped' +translation: + engine: anthropic + source: documents/vol3-standard-library/09-container-adapters.md + source_hash: 08bc8dd7591c4aec4f05629412e7bb5172af01aa85a2d35d3fd561fabaff6137 + token_count: 1648 + translated_at: '2026-06-15T09:17:12.996956+00:00' +--- +# Container Adapters: How `stack`, `queue`, and `priority_queue` Wrap Underlying Containers + +## Adapters are not Containers: They are Restricted Shells Around Underlying Containers + +`stack`, `queue`, and `priority_queue` are officially called **container adapters** in the standard, not independent containers. The distinction is this: a true container (like `vector` or `list`) owns its data and determines its storage strategy; an adapter does not invent its own storage. Instead, it **holds an underlying container** and wraps it in a restricted interface, forcing you to access data in a specific way (stack, queue, or priority queue). + +This "restriction" is the key, and the reason adapters exist. `stack` only exposes `push`, `pop`, and `top`, all occurring at the same end. Physically, it is impossible to steal an element from the middle—this turns "Last-In-First-Out" from a convention into a structural guarantee, blocking misuse at the compiler level. Similarly, `queue` guarantees First-In-First-Out, and `priority_queue` guarantees you always get the highest priority element. The cost is the loss of random access, but in exchange, you get predictable access patterns and an interface that prevents abuse. So, the decision to use an adapter boils down to this: **Do I only need this specific access mode, and do I want the type system to block other operations?** + +## `stack` and `queue`: Building LIFO/FIFO with Operations at the Ends + +The interface of an adapter is essentially a renaming of specific operations from the underlying container. `stack` is Last-In-First-Out: `push` adds an element to the back, `top` peeks at the back, and `pop` removes the back. Since all three actions occur at the container's `back`, it requires the underlying container to support `back`, `push_back`, and `pop_back`. `queue` is First-In-First-Out: elements enter via `push_back` at the `back` and leave via `front`/`pop` at the `front`. Thus, it additionally requires the underlying container to support `front` and `pop_front`. + +| Adapter | Semantics | Required Underlying Container Support | Default Underlying | +|--------|-----------|----------------------------------------|-------------------| +| `stack` | LIFO | `back`, `push_back`, `pop_back` | `deque` | +| `queue` | FIFO | `front`, `back`, `push_back`, `pop_front` | `deque` | +| `priority_queue` | Priority | `front`, `push_back`, `pop_back` + **Random Access Iterator** | `vector` | + +Why is `deque` the default for `stack` and `queue`? Because insertion and deletion at both ends are $O(1)$, satisfying the needs of `stack` (which only uses `back`) and `queue` (which uses `front` and `back`). Furthermore, `deque` avoids the cost of bulk reallocation that `vector` incurs during expansion. Here is a counter-intuitive point worth noting: **`queue` cannot use `vector` as its underlying container**, because `vector` lacks `pop_front`. To pop from the front of a `vector`, you would need `erase(begin())`, which is $O(n)$ and isn't even provided as a member function by the standard library. To swap the underlying container for `queue`, your only legal choices are `deque` or `list`. `stack` is much more flexible; `deque`, `vector`, or `list` all work because they satisfy the three requirements. + +## `priority_queue`: Underlying Container Plus Heap Algorithms, This is the Key + +Of the three adapters, `priority_queue` is the most worth dissecting, as its implementation best embodies the pattern "adapter = underlying container + standard library algorithms." It is not some mysterious data structure; essentially, it is "a contiguous container + a few heap functions from ``." Specifically, `push` is equivalent to `push_back` followed by `push_heap`; `pop` is equivalent to `pop_heap` followed by `pop_back`; and `top` just returns `front`. The heap algorithms maintain the "heap property," ensuring that `front` is always the current highest priority element. + +We can derive the complexity directly from this implementation. `top` reads the first element directly, so it is $O(1)$. `push` appends to the end in constant time, and `push_heap` floats the new element up at most the tree height of $\log n$ layers, resulting in $O(\log n)$. In `pop`, `pop_heap` first swaps the first and last elements, then sinks the new first element down, again traversing at most $\log n$ layers, plus one `pop_back`, resulting in overall $O(\log n)$. This also explains why the underlying container for `priority_queue` **must** support random access iterators. Heap sinking and floating require jumping by index within an array (parent node $(i-1)/2$, children $2i+1$/$2i+2$). Linked lists cannot achieve this $O(1)$ positioning, so the underlying choices are limited to `vector` or `deque`, with `vector` as the default (contiguous memory is cache-friendly, making heap operations faster). + +The default comparator is `less`, resulting in a **max-heap**—`top` returns the current maximum. To get a min-heap, simply swap the comparator for `greater`. This feature of "changing heap direction by swapping the comparator" is the most common usage pattern for `priority_queue`. + +## Try It Out: Default Max-Heap, Swap Comparator for Min-Heap + +Just saying "default max-heap" isn't concrete enough; let's run it to see exactly what `priority_queue` gives us. + +```cpp +#include +#include +#include + +int main() { + // Default: max-heap (std::less) + std::priority_queue max_heap; + // Min-heap (std::greater) + std::priority_queue, std::greater> min_heap; + + for (int val : {3, 1, 4, 1, 5, 9, 2, 6}) { + max_heap.push(val); + min_heap.push(val); + } + + std::cout << "Max-Heap pop order: "; + while (!max_heap.empty()) { + std::cout << max_heap.top() << " "; + max_heap.pop(); + } + std::cout << "\n"; + + std::cout << "Min-Heap pop order: "; + while (!min_heap.empty()) { + std::cout << min_heap.top() << " "; + min_heap.pop(); + } + std::cout << "\n"; + + return 0; +} +``` + +```text +Max-Heap pop order: 9 6 5 4 3 2 1 1 +Min-Heap pop order: 1 1 2 3 4 5 6 9 +``` + +With the same dataset, the default setup pushes the largest value, 9, to the top. After switching to `greater`, the smallest value, 1, rises to the top. Notice that the pop order is **sorted**—this is essentially the process of heap sort. `priority_queue` spits out the current extreme value on every `pop`. Continuously popping until empty yields a sorted sequence. Because the underlying structure is a heap, `priority_queue` is often used as "online heap sort": you can push elements and retrieve the current extreme value at any time. `top` is $O(1)$, and insertion/deletion are $O(\log n)$, making it a core data structure for many algorithms (Dijkstra, merging K sorted lists, Top-K). + +## C++23 Upgrade: `push_range` for Bulk Insertion + +C++23 adds `push_range` to all three adapters, allowing you to push an entire range at once. For `stack` and `queue`, this is just syntactic sugar for a loop of `push` calls. However, for `priority_queue`, it offers a tangible complexity advantage that is worth discussing. + +The reason lies in the cost of maintaining the heap property. If you take a range of N elements and loop `push` N times, each `push` (which calls `push_heap`) is $O(\log n)$, resulting in a total of $O(n \log n)$. The `push_range` approach, however, appends the entire range to the underlying container at once (`append`, $O(n)$) and then performs a single `make_heap` (also $O(n)$), resulting in a total of only $O(n)$. When the number of elements is large, the difference is significant. + +```cpp +#include +#include +#include + +int main() { + std::priority_queue pq; + + // C++23: push_range + // Complexity: O(N) vs O(N log N) for individual pushes + std::vector source = {3, 1, 4, 1, 5, 9, 2, 6}; + pq.push_range(source); + + std::cout << "Top after push_range: " << pq.top() << "\n"; + return 0; +} +``` + +Requires C++23 standard library support (a newer libstdc++ or libc++). Compile with `-std=c++23`. In older environments, falling back to a loop of `push` works fine; the behavior is identical, just slower for large datasets. + +## The Rationale for Choosing Underlying Containers + +The vast majority of the time, the defaults are optimal—`stack` and `queue` use `deque`, and `priority_queue` uses `vector`. These are the choices selected by the committee for good reason. If you need to swap them, it is usually for one of two reasons. One is `priority_queue` trying to avoid the default `vector` expansion copies—you can reserve space for the underlying `vector`. However, the adapter doesn't expose `reserve` directly, so you must construct the underlying container first and then `move` it in. The other reason is if the element type is not friendly to `vector` (e.g., very large or expensive to move); in that case, `priority_queue` can use `deque` as the underlying container. Scenarios for swapping the underlying container for `stack`/`queue` are even rarer, unless you explicitly want to save memory (using `list` to avoid pre-allocation), in which case the default `deque` is usually fine. + +## Wrapping Up + +The core of container adapters can be summed up in one phrase: **underlying container + restricted interface, trading restriction for semantic guarantees.** `stack` and `queue` expose one or both ends of a container as a stack or queue. `priority_queue` goes a step further, using the heap functions from `` to wrap a contiguous container into a priority queue—`top` is $O(1)$, insertion/deletion are $O(\log n)$, it defaults to a max-heap, and swapping the comparator turns it into a min-heap. Two usage caveats to remember: First, `top` is just a peek; to actually remove the element, you must follow it with `pop`. Second, `priority_queue` lacks interfaces for "erase arbitrary element" or "find by value." If you need these (e.g., to revoke an element midway), you should be using `std::set` or `std::multiset`, not `priority_queue`. In the next article, we will shift our focus from classic containers to the new members added to the container family in C++23/26—`std::flat_map`, `std::flat_set`, and `std::mdspan`. + +Want to run this yourself? Check out the online example below (runnable, with x86 assembly output): + + + +## References + +- [std::stack — cppreference](https://en.cppreference.com/w/cpp/container/stack) +- [std::queue — cppreference](https://en.cppreference.com/w/cpp/container/queue) +- [std::priority_queue — cppreference](https://en.cppreference.com/w/cpp/container/priority_queue) +- [std::priority_queue::push_range (C++23) — cppreference](https://en.cppreference.com/w/cpp/container/priority_queue/push_range) +- [std::push_heap / std::make_heap (Heap Algorithms) — cppreference](https://en.cppreference.com/w/cpp/algorithm/push_heap) diff --git a/documents/en/vol3-standard-library/10-new-containers-cpp23-26.md b/documents/en/vol3-standard-library/10-new-containers-cpp23-26.md new file mode 100644 index 000000000..e54289fbd --- /dev/null +++ b/documents/en/vol3-standard-library/10-new-containers-cpp23-26.md @@ -0,0 +1,120 @@ +--- +chapter: 7 +cpp_standard: +- 23 +- 26 +description: 'A review of the new members added to the containers family in C++23/26: + `flat_map` flattens the red-black tree into a sorted `vector` (ordered and cache-friendly, + but O(n) insertion/deletion), `inplace_vector` is a fixed-capacity container without + heap allocation (C++26), `mdspan` provides a multidimensional view (C++23, with + `submdspan` slicing in C++26), and the `hive` proposal is still in progress.' +difficulty: intermediate +order: 10 +platform: host +prerequisites: +- map 与 set 深入 +- unordered_map 与 set 深入 +- span:非拥有的连续视图 +- array:编译期固定大小的聚合容器 +reading_time_minutes: 9 +related: +- 容器选择指南:按操作、内存与失效规则挑对容器 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: 'New Standard Containers: flat_map, inplace_vector, and mdspan' +translation: + engine: anthropic + source: documents/vol3-standard-library/10-new-containers-cpp23-26.md + source_hash: 4523da607c36be4c2dea1098f2d4dfdc971c898009bca41835d083bfb92bd015 + token_count: 1880 + translated_at: '2026-06-15T09:18:18.938018+00:00' +--- +# New Standard Containers: flat_map, inplace_vector, and mdspan + +## What this article covers: Long-standing gaps filled by C++23/26 + +The standard library's `std::vector` family has remained stable for over twenty years since C++98, and the suite of `std::map`/`std::set`/`std::unordered_map` has barely changed. However, practical development has several long-standing gaps: Can ordered associative containers ditch the red-black tree for contiguous storage to be cache-friendly? Between fixed-size `std::array` and heap-allocating `std::vector`, can we have a middle ground where the capacity is known at compile time, the length is variable at runtime, and it never touches the heap? For multidimensional data (matrices, images, voxels), can we get a non-owning multidimensional view like `std::span`? C++23 and C++26 have filled these gaps—this article covers `std::flat_map`/`std::flat_set`, `std::inplace_vector`, and `std::mdspan`, which have already been standardized, with a brief mention of `std::hive`, which is still on the way. + +A quick heads-up: these components are very new. `std::flat_map` and `std::mdspan` are from C++23 (requiring relatively recent libstdc++/libc++), and `std::inplace_vector` is from C++26. If your toolchain isn't up to date, they won't compile. Understanding their design philosophy is more important than immediate usability—once you upgrade to a C++23/26 toolchain, these will be ready-to-use ammunition. All examples in this article have been tested on GCC 16.1.1 (libstdc++, C++23 / C++26): `flat_map` and `mdspan` have been available since GCC 15, while `inplace_vector` requires GCC 16. + +## flat_map / flat_set: Flattening the red-black tree into a sorted vector (C++23) + +First, let's look at `std::flat_map` and `std::flat_set` (along with `std::flat_multimap`/`std::flat_multiset`, totaling four). Their motivation is straightforward: as discussed in [Deep Dive into map and set](06-map-set-deep-dive.md), `std::map`/`std::set` are implemented as red-black trees underneath. Every element is a heap node, linked by pointers. Lookups and traversals jump between nodes, resulting in poor cache hit rates. Although the complexity is O(log n), the constant factor is heavily impacted by cache unfriendliness. `std::flat_map` solves this by **flattening the entire tree into a sorted contiguous container** (defaulting to `std::vector`), where key-value pairs are arranged adjacently in memory. Lookups use binary search (O(log n)), but thanks to contiguous memory, it is cache-friendly, resulting in a smaller constant factor than red-black trees. + +Interface-wise, `std::flat_map` is a **near drop-in replacement for `std::map`**—`operator[]`, `at`, `count`, `find`, and range iteration are all present. Even ordered traversal works, making migration costs low. However, the trade-offs are clear, stemming entirely from the fact that "the underlying container is contiguous." First, **insertion and deletion are O(n)**: inserting an element into the middle of a sorted array requires shifting all subsequent elements; deleting one requires shifting them forward. This contrasts sharply with the O(log n) insertion/deletion of red-black trees, so `flat_map` is suitable for scenarios where "lookups and traversals far outnumber insertions and deletions." Second, **iterators and references are unstable**: any insertion or deletion might trigger moving or even reallocation, just like `std::vector`, invalidating all iterators—whereas `std::map`'s iterators never invalidate. In short, `flat_map` trades "expensive mutations + aggressive invalidation" for "faster constant factors in lookup and traversal." When data volume is small and reads outnumber writes, this trade-off is worth it. + +```text +[Diagram: flat_map vs map structure comparison] +``` + +## inplace_vector: Fixed-capacity, heap-avoiding variable-length container (C++26) + +The second is `std::inplace_vector`, which entered the standard in C++26 (proposal P0843). It fills the gap between `std::array` and `std::vector`: `std::array` has a size fixed at compile time and cannot change; `std::vector` can change size but requires heap allocation (allocating a new block, copying, and freeing the old one during expansion). Often, what you need is "capacity known at compile time, variable size at runtime, but absolutely no heap touching"—`std::inplace_vector` does exactly this. Its elements are stored **directly inside the object** (the object itself occupies the space of `N` elements, placed on the stack or in static storage). At runtime, you can add or remove elements between 0 and N, without `new`, without reallocation, and without copying or moving. + +Its most appealing property is: **when `T` is trivially copyable, `std::inplace_vector` itself is also trivially copyable**. This means it can be `memcpy`'d as a whole, stored in registers, or safely handed to DMA—features critical for embedded and systems programming. As discussed in [Deep Dive into array](02-array.md), `inplace_vector` enjoys the same benefits of "contiguous memory + trivially copyable," whereas `std::vector` cannot because it holds a heap pointer and is not trivially copyable. Behavior when exceeding capacity is also designed to be restrained: `push_back` exceeding N throws `std::bad_alloc` (or degrades to `terminate` if exceptions are disabled). To avoid exceptions, you can use C++26's `try_push_back`/`try_emplace_back`, which return an error indicator instead of throwing, making them suitable for freestanding environments. + +```text +[Diagram: inplace_vector memory layout] +``` + +```text +[Diagram: inplace_vector vs array vs vector comparison] +``` + +```text +[Diagram: inplace_vector trivially copyable property] +``` + +The boundary between `std::array` and `std::inplace_vector` needs to be clear: `std::array`'s size is always N (fixed length); `std::inplace_vector`'s capacity is capped at N, but its size is variable at runtime between 0 and N. Use `array` for fixed length; use `inplace_vector` for "known upper bound + runtime variable + no heap allocation." + +## mdspan: The multidimensional version of span (C++23, slicing in C++26) + +The third is `std::mdspan`, standardized in C++23 (proposal P0009). As discussed in [Deep Dive into span](08-span.md), `std::span` is a one-dimensional contiguous memory view, but reality is full of 2D and 3D data—matrices, images, voxel fields, tensors. In the past, we had to use a raw 1D pointer and manually calculate subscripts (`data[y * width + x]`), which was ugly and prone to mixing up rows and columns. `std::mdspan` wraps "a contiguous block of memory + a multidimensional shape" into a view type, allowing direct access using multidimensional subscripts `m[i, j]`. It involves zero copying, holds no data, and only describes "how to interpret this memory as multidimensional." + +It has four template parameters: element type, `Extents` (shape, the size of each dimension), `LayoutPolicy` (how to map multidimensional subscripts to a 1D offset, default `layout_right` i.e., row-major, C/C++ style), and `AccessorPolicy` (how to read/write elements, default direct access). Shape is described by `std::extents`, where compile-time known dimension sizes are filled with constants, and runtime-known ones use `std::dextent`; if that's too much hassle, you can use `std::dextents`, meaning "all Rank dimensions are dynamic." Access uses the **multidimensional bracket subscript** `m[i, j]` (relying on the C++23 multidimensional `operator[]` language feature P2128), not the old `operator()`—the latter might imply returning a sub-view, whereas `mdspan` directly calculates the multidimensional index into a 1D offset and returns a reference to the element. A common pitfall: note that it uses square brackets `[]`, not function calls `()`; early `mdspan` reference implementations (like Kokkos) did use `()`, but the C++23 standard unified it to multidimensional `[]`. This is why many older tutorials and blogs still write `()`—copying them will result in compilation errors. + +```text +[Diagram: mdspan concept and usage] +``` + +```text +[Diagram: mdspan Extents and Layout] +``` + +```text +[Diagram: mdspan multidimensional subscript access] +``` + +A pitfall worth mentioning: **`submdspan` (slicing) is C++26, not C++23**. When `mdspan` landed in C++23, the functionality to slice rows, columns, or sub-blocks didn't make it and was moved to C++26 (P2630). So, if you want to extract a row in C++23, you still have to calculate the offset manually; you'll need to wait for a C++26 toolchain to use zero-copy slicing like `submdspan`. The greater significance of `mdspan` lies in it being the foundation for `std::linalg` (linear algebra library)—in future standards, matrix operation APIs will be built on top of `mdspan`. + +## Still on the way: hive and other proposals + +Finally, a mention of something often discussed but **not yet in the standard**: `std::hive` (from Matt Bentley's `plf::hive`, proposals P0909/P2826). It is a "node container" designed for stable element addresses (insertions/deletions don't affect other elements' addresses), fast erasure, and cache-friendly traversal (organizing nodes in blocks rather than pure linked lists). It fits scenarios where "you need to hold references to elements for a long time and also frequently insert/delete." As of C++26, it is still a proposal and has not been adopted—if you want to use it now, you must resort to the third-party `plf::hive` library. I mention this here to indicate the direction: the standards committee is seriously considering "node containers better than list," but it is not yet a member of the `std::` family, so don't write "C++26's hive" in articles or resumes. + +## Wrapping up + +This wave of new containers fills specific gaps: `std::flat_map` is for scenarios wanting "ordered + cache-friendly" (cost is O(n) mutations and vector-like invalidation); `std::inplace_vector` fills the middle ground of "known capacity cap + runtime variable length + absolutely no heap allocation" (C++26, trivially copyable properties are sweet for embedded); `std::mdspan` provides a zero-copy view type for multidimensional data (C++23, slicing `submdspan` waits for C++26). All three rely on relatively new toolchains; `flat_map` needs C++23 library support, and `inplace_vector` needs C++26, so verify your compiler and standard library versions before deploying. The container thread ends here—from `std::vector` to new standard containers, we've covered the tools for storing data; next, Vol. 3 will turn to iterators and algorithms for "traversing and manipulating data." + +Want to try running these examples directly? Click the online demo below (you can run them and view the assembly): + + + +## Reference Resources + +- [std::flat_map — cppreference](https://en.cppreference.com/w/cpp/container/flat_map) +- [std::flat_set — cppreference](https://en.cppreference.com/w/cpp/container/flat_set) +- [std::inplace_vector (C++26) — cppreference](https://en.cppreference.com/w/cpp/container/inplace_vector) +- [std::mdspan — cppreference](https://en.cppreference.com/w/cpp/container/mdspan) +- [std::submdspan (C++26, P2630) — cppreference](https://en.cppreference.com/w/cpp/container/mdspan/submdspan) +- [Details of std::mdspan from C++23 — C++ Stories](https://www.cppstories.com/2025/cpp23_mdspan/) +- [plf::hive (Proposal library reference) — GitHub](https://github.com/mattreecebentley/plf_hive) diff --git a/documents/en/vol3-standard-library/11-initializer-lists.md b/documents/en/vol3-standard-library/11-initializer-lists.md new file mode 100644 index 000000000..ea1a363a3 --- /dev/null +++ b/documents/en/vol3-standard-library/11-initializer-lists.md @@ -0,0 +1,114 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +description: 'Deep dive into `std::initializer_list`: the compiler-generated read-only + view for {...}, shallow copies and `const` elements, the "move trap" where elements + cannot be moved into containers, overload resolution priority for brace initialization, + and its relationship with container constructors.' +difficulty: intermediate +order: 11 +platform: host +reading_time_minutes: 4 +related: +- vector 深入:三指针、扩容与迭代器失效 +- span:非拥有的连续视图 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: 'std::initializer_list: The Lightweight Sequence Behind Curly Braces' +translation: + engine: anthropic + source: documents/vol3-standard-library/11-initializer-lists.md + source_hash: 3559a8bcc57fe924d5db6f17a6544dd8c8d3d957e70172525613642c34fa59c0 + token_count: 1288 + translated_at: '2026-06-15T09:19:15.134431+00:00' +--- +# std::initializer_list: The Lightweight Sequence Behind Braces + +## What is initializer_list: A Read-Only View Generated by the Compiler for `{...}` + +`std::initializer_list` is the standard library type introduced in C++11 to support "braced list initialization". When you write `std::vector v = {1, 2, 3}` or `func({1.0, 2.0})`, the compiler constructs an `initializer_list` behind the scenes, representing the sequence `{1, 2, 3}`. It is an extremely lightweight object—essentially just a pointer and a length, similar to `std::string_view`, belonging to the category of "views that do not own data." + +```cpp +std::vector v = {1, 2, 3}; // Compiler generates std::initializer_list +``` + +There are three key properties: it **does not own** the elements (the elements live in a hidden underlying const array generated by the compiler), the elements are **const** (read-only), and copying it is a **shallow copy** (it copies the pointer and length, not the elements). These three rules dictate its entire behavior and hide its most famous pitfalls. + +## How Light is It: Shallow Copy, Read-Only Elements + +Copying an `initializer_list` is shallow—copying the list copies the internal pointer (and length), leaving the underlying const array untouched. Therefore, passing an `initializer_list` by value costs almost nothing, similar to passing a pointer. + +```cpp +void func(std::initializer_list list); // Passing by value is cheap (shallow copy) +``` + +But remember the "elements are const" rule: the elements inside an `initializer_list` are `const T&`. You cannot get non-const access. This seems harmless, but it digs a huge pit when combined with move semantics—we'll cover that in the next section. + +## The Move Trap: Elements in `{...}` Can Only Be Copied into Containers + +This is the classic `initializer_list` pitfall. You want to shove a few objects into a `vector`, so you write `std::vector v{obj1, obj2, obj3}` expecting modern C++ to efficiently move them—result: they are **copied** in. + +The root cause is "elements are const": `initializer_list` elements are `const T&`, while move constructors require `T&&` (non-const). When a `vector` constructs from an `initializer_list`, it must copy each const element into its own storage. You can't move from const, only copy. Even if you write `std::string{"s"}` in the braces, the object "moves into the initializer_list" (because the constructor accepts an rvalue), but once inside, it becomes const, so moving it into the vector requires a copy. + +Let's measure this to see exactly how many copies happen. We'll use a type that counts copies and moves: + +```cpp +struct Counter { + static int copies, moves; + // ... implementation details ... +}; +int Counter::copies = 0; +int Counter::moves = 0; +``` + +```cpp +// Scenario 1: Lvalues +Counter c1, c2, c3; +std::vector v1{c1, c2, c3}; +// Result: 6 copies, 0 moves +``` + +```cpp +// Scenario 2: Rvalues +std::vector v2{Counter{}, Counter{}, Counter{}}; +// Result: 3 copies, 3 moves +``` + +Let's compare these three scenarios. The first one `{c1, c2, c3}` (lvalues): 6 copies, 0 moves—3 copies to construct the `initializer_list` elements, then 3 more copies into the `vector`. The second `{Counter{}, Counter{}, Counter{}}`: 3 copies, 3 moves—the rvalues let the objects move into the `initializer_list` (saving 3 copies), but the step into the `vector` is still 3 copies because const can't move. The third `push_back` or `emplace_back`: 0 copies, 3 moves—bypassing `initializer_list` entirely, moving directly into the `vector` for zero copies. + +So remember this performance pitfall: **when putting several objects into a container, `{...}` still copies into the vector, only `push_back`/`emplace_back` gives zero copies**. When `T` is a heavy type (large `string`, large `vector`), this difference is real copy overhead. + +## Brace Priority: Why `{...}` Always Prefers Matching initializer_list + +`initializer_list` has an "overload preference": as long as a class has a constructor taking `std::initializer_list`, brace initialization will prioritize it, even if another constructor seems a better fit. The most classic crash site is `std::vector`: + +```cpp +std::vector v1(10, 0); // 10 elements, value 0 +std::vector v2{10, 0}; // 2 elements: 10 and 0 +``` + +`v1` is 10 zeros, `v2` is `{10, 0}`—same intent, but parentheses and braces give totally different results because braces prioritized the `initializer_list` constructor. This isn't a bug, it's the rule: brace initialization prioritizes `initializer_list` constructors when available. So when constructing containers, don't mix `()` and `{}`; if the intent differs, use different brackets. + +## Wrapping Up + +`std::initializer_list` is the lightweight view behind braced list initialization: non-owning, const elements, shallow copy. It makes syntax like `func({1, 2, 3})` elegant for passing to functions and containers, but "const elements" buries two points to remember—first is the move trap (`{...}` into containers always copies, heavy types need `push_back`), second is brace priority (with an `initializer_list` constructor, `{...}` will aggressively match). In the next post, we leave initialization behind and look at the memory layout of types themselves: object size and trivial types. + +Want to run it yourself? Check out the online example below (runnable, with assembly view): + + + +## References + +- [std::initializer_list — cppreference](https://en.cppreference.com/w/cpp/utility/initializer_list) +- [List initialization — cppreference](https://en.cppreference.com/w/cpp/language/list_initialization) diff --git a/documents/en/vol3-standard-library/12-object-size-and-trivial-types.md b/documents/en/vol3-standard-library/12-object-size-and-trivial-types.md new file mode 100644 index 000000000..9a70dfb0b --- /dev/null +++ b/documents/en/vol3-standard-library/12-object-size-and-trivial-types.md @@ -0,0 +1,224 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: We explain `sizeof`/`alignof` and memory padding, the precise distinctions + between trivial/trivially copyable/standard-layout, the decomposition of POD (Plain + Old Data), when `memcpy` is safe, and aggregate initialization vs. C++20 designated + initializers. +difficulty: intermediate +order: 12 +platform: host +reading_time_minutes: 7 +related: +- array:编译期固定大小的聚合容器 +tags: +- host +- cpp-modern +- intermediate +- 类型安全 +- 容器 +title: Object Size, Alignment, and Trivial Types +translation: + engine: anthropic + source: documents/vol3-standard-library/12-object-size-and-trivial-types.md + source_hash: 152da35221b5197e7ef3a825583be934ee6291a0739678f081a9e81d195efbd6 + token_count: 1635 + translated_at: '2026-06-15T09:20:18.710978+00:00' +--- +# Object Size, Alignment, and Trivial Types + +When writing low-level code, interfacing with C APIs, or optimizing memory usage, we often get tangled in a string of obscure terms: `alignof`, `sizeof`, `trivial`, `trivially copyable`, `standard-layout`, aggregates... These concepts seem fragmented, but they are actually an interconnected map: they determine an object's memory representation, copy semantics, whether it can be safely `memcpy`-ed, ABI compatibility with C structs, and initialization flexibility. In this post, we will straighten them out. + +## Size and Alignment: Why `sizeof` Isn't Always the Sum of Members + +`sizeof` reports the number of bytes an object **occupies in memory** (complete object representation, including necessary padding), while `alignof` reports the type's **alignment constraint** — the starting address of the object must be an integer multiple of `alignof`. To ensure every member lands on its required alignment boundary, padding may be inserted between members, as well as at the end of the structure. + +Let's look at a common example: + +```cpp +struct Bad { + char a; // 1 byte + // 3 bytes padding + int b; // 4 bytes + char c; // 1 byte + // 3 bytes padding +}; +``` + +If we swap the order, the padding increases: + +```cpp +struct Worse { + char a; // 1 byte + // 3 bytes padding + char c; // 1 byte + // 3 bytes padding + int b; // 4 bytes +}; +``` + +If we put the two `char`s together, we save padding: + +```cpp +struct Good { + char a; // 1 byte + char c; // 1 byte + // 2 bytes padding + int b; // 4 bytes +}; +``` + +The same members, just reordered: `Bad` takes 12 bytes, `Good` takes only 8 bytes — this is where the "arrange member order to save memory" rule comes from. The overall alignment of a structure is the **maximum alignment** among its members. The compiler also adds padding at the end to ensure `sizeof` is a multiple of `alignof` (this affects the spacing of elements in an array). + +We can use `alignas` to force a specific alignment, for example, specifying 16-byte alignment for a SIMD buffer: + +```cpp +struct alignas(16) Vec4 { + float x, y, z, w; +}; +``` + +Be careful with `alignas`: increasing alignment changes `sizeof` and the ABI. Placing an object at an unaligned address on hardware that requires aligned access can cause an immediate crash. + +## trivial / trivially_copyable / standard-layout: Three Confusing Concepts + +The C++ standard breaks down a set of "type properties" to precisely express "how objects of this type behave in memory." This is a design aspect of C++11 (splitting the historical POD concept into several distinct concerns). Let's first clarify the terms that are often confused: + +- **trivial type**: Special member functions (default constructor, copy/move constructors, assignment, destructor) are all compiler-generated; there is no custom logic. In other words, construction/copy/destruction generates no runtime code — the object's bits are its entirety, with no hidden actions. +- **trivially_copyable type**: Can be safely copied byte-by-byte via `memcpy` (after copying, the destination has the same object representation and can be properly destroyed). **This is the criterion for whether `memcpy` can be used.** +- **standard-layout type**: Has predictable memory layout rules (members arranged in declaration order, no complex access control / virtual inheritance / multiple base classes causing uncertain layout). **This is the criterion for layout compatibility with C structs.** + +A key fact: the old concept `POD` (Plain Old Data) was split in C++11 into `trivial` and `standard-layout`. `std::is_pod` is semantically just "both trivial and standard-layout." Therefore, safety assumptions related to ABI and C interoperability are now checked using `std::is_trivially_copyable` and `std::is_standard_layout` respectively. + +Here is an example connecting them: + +```cpp +struct S { + int x; + float y; +}; +static_assert(std::is_trivial_v); // true +static_assert(std::is_trivially_copyable_v); // true +static_assert(std::is_standard_layout_v); // true +``` + +Compare this with a non-trivial one: + +```cpp +struct T { + int x; + T(int v) : x(v) {} // User-defined constructor -> non-trivial +}; +static_assert(!std::is_trivial_v); // true +static_assert(std::is_trivially_copyable_v); // true (can still memcpy) +static_assert(std::is_standard_layout_v); // true +``` + +To emphasize an easy mistake: **trivial ≠ trivially_copyable**. The former emphasizes the "triviality" of special members (especially the default constructor), while the latter emphasizes whether byte-wise copying is safe. To judge if you can `memcpy`, use `std::is_trivially_copyable`, not `std::is_trivial`. + +## Let's Run: Testing Layout and Type Properties + +Just talking about `alignof` and `sizeof` is too abstract. Let's use `static_assert` to nail these assumptions into compile-time, and then run it to see: + +```cpp +struct A { char a; int b; char c; }; +struct B { char a; char c; int b; }; +struct C { char a; char c; char _pad[2]; int b; }; +struct Vec4 { float x, y, z, w; }; +struct S { int x; float y; }; +struct T { int x; T(int v) : x(v) {} }; + +int main() { + static_assert(sizeof(A) == 12); + static_assert(sizeof(B) == 8); + static_assert(sizeof(C) == 8); + static_assert(sizeof(Vec4) == 16); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_standard_layout_v); + static_assert(!std::is_trivial_v); +} +``` + +All `static_assert`s pass (compilation success implies A=12, B=8, C=8, Vec4=16, S is both trivially copyable and standard-layout, T is non-trivial — all assumptions are correct). This is the correct way to use this knowledge: **write your assumptions about layout/types into code using `static_assert`**. If an assumption changes, the compiler stops you, which is much more reliable than comments. + +## Aggregates and Designated Initializers: From Braces to C++20 + +An aggregate is a convenient type category: it allows direct initialization of members using braces (aggregate initialization), which is extremely intuitive when writing data descriptions (configuration structures, register maps), and naturally suitable for `constexpr`. Intuitively, an aggregate is a type with "no user-defined constructors, no virtual functions, all non-static members are public, and no base classes (or base classes meet standard-layout restrictions)" — the compiler can simply copy initialization values into the object representation in member order. + +```cpp +struct Config { + int baudrate = 115200; + int timeout_ms = 1000; +}; + +Config cfg = { 9600, 500 }; // Aggregate initialization +``` + +C++20 introduced **designated initializers** (C had this long ago, C++20 finally adopted it formally), making aggregate initialization more readable and insensitive to member order: + +```cpp +Config cfg2 = { .baudrate = 9600, .timeout_ms = 500 }; +Config cfg3 = { .timeout_ms = 2000 }; // baudrate uses default +``` + +Nested structures and array indices can also be specified, which is particularly handy when initializing complex layouts (register tables, protocol headers): + +```cpp +struct Mode { int mode; int flags; }; +struct Regs { Mode mode; int prescaler[2]; }; + +Regs r = { + .mode = { .mode = 1, .flags = 0 }, + .prescaler = { [0] = 10, [1] = 20 } +}; +``` + +Note: Designated initializers only apply to **aggregate types**. Classes with user-defined constructors cannot use this syntax. + +## Putting It All Together: Practical Principles for Type Properties + +Let's string these points into a few actionable principles. + +First, when defining data structures to interact with C or go through DMA (register maps, protocol headers, serialization formats), ensure they are **standard-layout** (predictable layout) and preferably **trivially_copyable** (can be `memcpy`-ed or `reinterpret_cast`-ed from a block of memory). Avoid virtual functions, private non-static members, and custom constructors/destructors/copies. Use `static_assert` at the interface to nail down these invariants: + +```cpp +struct PacketHeader { + uint32_t len; + uint32_t seq; + uint8_t type; +}; +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +``` + +Second, alignment affects `sizeof` and array layout. If hardware or DMA requires special alignment (16-byte cache line, SIMD), use `alignas` to specify it explicitly, and remember that it changes `sizeof` and the ABI. + +Third, prefer braces and designated initializers for initialization. They are readable, resilient to member reordering, and often `constexpr`. + +Fourth, copy semantics: **only types that are `trivially_copyable` can be safely `memcpy`-ed**. For classes with virtual functions, non-trivial destructors, or special members, do not perform binary copies; strictly use construction/copy/assignment. + +## Summary + +- `alignof` determines alignment requirements, `sizeof` reports actual occupation (including padding); arranging member order wisely saves padding. +- `trivial`, `trivially_copyable`, and `standard-layout` are the standard's fine-grained divisions of type properties: to `memcpy` check `trivially_copyable`, for C layout compatibility check `standard-layout`, `POD` = both trivial and standard-layout. +- Aggregate initialization is convenient; C++20 designated initializers are more readable and order-independent. +- Write assumptions about layout and types into code using `static_assert`, letting the compiler guard these invariants for you. + +Want to try it out right now? Open the online example below (you can run it and view the assembly): + + + +## Reference Resources + +- [Type traits — cppreference](https://en.cppreference.com/w/cpp/header/type_traits) +- [Standard layout types — cppreference](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout) +- [Designated initializers (C++20) — cppreference](https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers) diff --git a/documents/en/vol3-standard-library/13-custom-allocators.md b/documents/en/vol3-standard-library/13-custom-allocators.md new file mode 100644 index 000000000..d50416466 --- /dev/null +++ b/documents/en/vol3-standard-library/13-custom-allocators.md @@ -0,0 +1,231 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 17 +- 20 +description: 'Deep dive into custom allocators: mechanisms and trade-offs of Bump, + Pool, and Stack strategies, placement new with object construction and destruction, + the C++17 `std::pmr` `memory_resource` system (`monotonic`/`pool`) and `pmr` containers, + and when to manage memory manually.' +difficulty: advanced +order: 13 +platform: host +reading_time_minutes: 7 +related: +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- advanced +- 内存管理 +- 容器 +title: 'Custom Allocators & PMR: Managing Memory Yourself' +translation: + engine: anthropic + source: documents/vol3-standard-library/13-custom-allocators.md + source_hash: a035d00a57044775e7d5dba72a7de2bb6c5efa0efef3e94f42578aef5907b024 + token_count: 1666 + translated_at: '2026-06-15T09:21:25.554976+00:00' +--- +# Custom Allocators & PMR: Managing Your Own Memory + +## Why We Need Custom Allocators + +Default `new`/`delete` are convenient, but they have weaknesses: indeterminate allocation timing (potentially blocking real-time tasks), heap fragmentation, poor locality, and a one-size-fits-all approach. When you encounter these requirements, default allocators fall short—real-time tasks cannot be stalled by sporadic `malloc` calls, you might want to allocate everything at startup to avoid runtime allocation, you need high-frequency allocation of fixed-size small objects, or you want to dedicate a large block of memory to a specific module for easier tracking. In these scenarios, managing your own memory becomes an essential skill for engineers. + +Allocators boil down to two things: **allocation** (giving out unused memory) and **deallocation** (taking it back). In C++, we also handle alignment and object construction/destruction. Let's first look at three classic strategies to understand the mechanisms, then look at the C++17 standard library solution: `std::pmr`. + +## Three Classic Allocation Strategies + +### Bump (Linear) Allocator + +The simplest allocator: maintain a pointer, move it up to allocate, and do not support freeing individual objects (only a global reset). Allocation is O(1), suitable for startup or short-lived tasks. + +```cpp +// Bump Allocator: Linear allocation, no individual free +class BumpAllocator { + void* base; // Start of memory block + size_t offset; // Current offset + size_t size; // Total size +public: + BumpAllocator(void* base, size_t size) : base(base), offset(0), size(size) {} + + void* allocate(size_t n, size_t alignment) { // n bytes, alignment alignment + // Align current offset up + size_t aligned_offset = (offset + alignment - 1) & ~(alignment - 1); + if (aligned_offset + n > size) return nullptr; // OOM + void* ptr = static_cast(base) + aligned_offset; + offset = aligned_offset + n; + return ptr; + } + + void reset() { offset = 0; } // Reset all allocations +}; +``` + +It cannot free individual objects (unless you add tagging/rollback), but the implementation is extremely simple and fast. It fits scenarios where you "allocate a bunch, use them, and reset everything at once." + +### Fixed-Size Memory Pool (Free-list) + +For many small objects of the same size (message nodes, connection objects), use a fixed-size pool: each slot is a fixed size, and when freed, the slot is linked back to the free list. Allocation/deallocation are both O(1) with minimal fragmentation. + +```cpp +// Fixed-size pool (Free-list) +class PoolAllocator { + struct Slot { Slot* next; }; // Free list node + Slot* free_list; +public: + PoolAllocator(void* base, size_t block_size, size_t count) { + // Initialize free list: chain all blocks + free_list = static_cast(base); + for (size_t i = 0; i < count - 1; ++i) + free_list[i].next = &free_list[i + 1]; + free_list[count - 1].next = nullptr; + } + + void* allocate() { + if (!free_list) return nullptr; // OOM + Slot* slot = free_list; + free_list = free_list->next; + return slot; + } + + void deallocate(void* ptr) { + if (!ptr) return; + Slot* slot = static_cast(ptr); + slot->next = free_list; + free_list = slot; + } +}; +``` + +`Slot` must contain alignment and control information; for thread safety, you need to add locks or go lock-free. + +### Stack (LIFO) Allocator + +When allocation/deallocation follows a Last-In-First-Out (LIFO) pattern, it's fastest, supporting "mark + rollback to mark." Ideal for frame allocation (allocate per frame, reclaim at frame end) or short-lived chains. Its `allocate` is like Bump (move pointer + align), adding `mark`/`rollback`: + +```cpp +// Stack Allocator: LIFO, supports mark/rollback +class StackAllocator { + void* base; + size_t offset; + size_t size; +public: + size_t mark() const { return offset; } // Save current state + + void rollback(size_t saved_offset) { // Restore state + offset = saved_offset; + } + + void* allocate(size_t n, size_t alignment) { + size_t aligned_offset = (offset + alignment - 1) & ~(alignment - 1); + if (aligned_offset + n > size) return nullptr; + void* ptr = static_cast(base) + aligned_offset; + offset = aligned_offset + n; + return ptr; + } +}; +``` + +Trade-offs: Bump is simplest but lacks individual free; Pool fits fixed-size high-frequency; Stack fits LIFO lifetimes. They all solve "how to efficiently manage a pre-allocated block of memory." + +## Placement New & Object Construction/Destruction + +Allocators only give raw memory (bytes); object construction/destruction is your business—use placement new to construct and explicitly call the destructor: + +```cpp +// Allocating raw memory vs constructing objects +void* raw = allocator.allocate(sizeof(MyObj), alignof(MyObj)); // 1. Allocate memory +MyObj* obj = new(raw) MyObj(arg1, arg2); // 2. Construct object (placement new) +// ... use obj ... +obj->~MyObj(); // 3. Destroy object +allocator.deallocate(raw, sizeof(MyObj)); // 4. Return memory +``` + +Remember: **Allocation ≠ Construction**. `allocate` gives memory, `new (ptr) T` constructs; `ptr->~T()` destroys, `deallocate` returns memory. This four-step "allocate / construct / destroy / deallocate" sequence is the core of hand-written allocators and the standard library allocator concept. + +## The Standard Library Answer: std::pmr (C++17) + +Hand-writing allocators helps you understand the mechanism, but to actually use "your own allocation strategy" in STL containers, writing a full `std::allocator` compatible type (a bunch of typedefs, `allocate()`/`deallocate()`) is tedious. C++17 offers a better solution: **std::pmr (polymorphic memory resource)**. + +The core of pmr is `std::pmr::memory_resource`—an abstract base class providing `allocate`/`deallocate` interfaces (you inherit from it to implement your own strategy). The standard library comes with several ready-made implementations: + +- `std::pmr::monotonic_buffer_resource`: The Bump allocator mentioned earlier, linear allocation on a stack/static buffer, extremely fast, no individual free, suitable for frame allocation or one-off tasks. +- `std::pmr::unsynchronized_pool_resource` / `synchronized_pool_resource`: Fixed-size pools, suitable for many small objects of the same size (use the synchronized version for multithreading). +- `std::pmr::null_memory_resource`: Borrows but never returns, used for "prohibit allocation from here on" scenarios. + +Then there are **pmr containers**: `std::pmr::vector`, `std::pmr::string`, `std::pmr::list`, etc., which use `std::pmr::polymorphic_allocator` internally and accept a `memory_resource*` at construction. You can change the allocation strategy without changing the container type (they are all `std::pmr::vector`), just swap the resource—this is pmr's biggest advantage over hand-written allocator templates: **type erasure, runtime strategy switching**. + +```cpp +// Using pmr: runtime pluggable allocator +#include + +// 1. Prepare memory +std::byte buffer[4096]; +std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)}; // Strategy: Bump + +// 2. Create container using the resource +std::pmr::vector vec{&pool}; // All allocations come from buffer + +vec.push_back(42); // No global heap involved +``` + +## Let's Run It: pmr::vector with monotonic buffer + +Let's run this to confirm that `pmr::vector` actually allocates from the stack buffer: + +```cpp +#include +#include +#include + +int main() { + // 1. Reserve stack memory + std::byte buffer[4096]; // Raw memory on stack + + // 2. Create monotonic_buffer_resource (Bump allocator) + std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)}; + + // 3. Create pmr::vector using this resource + std::pmr::vector vec{&pool}; + + // 4. Push some elements + for (int i = 0; i < 100; ++i) { + vec.push_back(i); + } + + std::cout << "Vector size: " << vec.size() << "\n"; + std::cout << "Buffer address: " << (void*)buffer << "\n"; + std::cout << "Vector data address: " << vec.data() << "\n"; + // Verify: vec.data() should be inside [buffer, buffer + 4096) +} +``` + +```text +Vector size: 100 +Buffer address: 0x7ffd12345678 +Vector data address: 0x7ffd12345678 +``` + +All elements of this vector come from that 4096-byte stack buffer, with zero global `new` calls. This is the typical usage of pmr + monotonic: feed a pre-allocated block of memory (stack, static area, or self-managed heap block) to a container to gain deterministic allocation behavior, zero fragmentation, and zero global heap overhead. Swap the resource (e.g., to a pool) to swap strategies without changing a single line of container code. + +## Wrapping Up + +The core of custom allocators is "managing the allocation/deallocation of a block of memory yourself." Three classic strategies—Bump (fast, no individual free), Pool (fixed-size high-frequency), and Stack (LIFO)—each have their use cases. Once you understand them, for use in STL, prioritize C++17's `std::pmr`: `memory_resource` abstraction + standard implementations (monotonic/pool) + pmr containers for runtime strategy switching and type explosion avoidance. Hand-written allocators are for understanding mechanisms or covering niche needs pmr doesn't; for常规 scenarios, pmr is sufficient. This concludes our container deep dive; next, we move to the standard library's iterator and algorithm system. + +Want to run this directly and see the effect? Open the online example below (runnable, with assembly view): + + + +## References + +- [std::pmr (memory_resource) — cppreference](https://en.cppreference.com/w/cpp/memory/resource) +- [monotonic_buffer_resource — cppreference](https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource) +- [polymorphic_allocator — cppreference](https://en.cppreference.com/w/cpp/memory/polymorphic_allocator) diff --git a/documents/en/vol3-standard-library/03-char8-t-utf8.md b/documents/en/vol3-standard-library/30-char8-t-utf8.md similarity index 98% rename from documents/en/vol3-standard-library/03-char8-t-utf8.md rename to documents/en/vol3-standard-library/30-char8-t-utf8.md index 7240911b3..9de179448 100644 --- a/documents/en/vol3-standard-library/03-char8-t-utf8.md +++ b/documents/en/vol3-standard-library/30-char8-t-utf8.md @@ -11,7 +11,7 @@ order: 3 platform: host prerequisites: - 卷一:std::string 与字符串字面量基础 -reading_time_minutes: 7 +reading_time_minutes: 6 tags: - host - cpp-modern @@ -19,11 +19,11 @@ tags: - 类型安全 title: char8_t and UTF-8 Strings translation: - source: documents/vol3-standard-library/03-char8-t-utf8.md - source_hash: bf65e1fa69d057d8e2387796ce4ed2c2c677e348f2808d359b0b024109c38afc - translated_at: '2026-06-14T00:19:58.325857+00:00' engine: anthropic + source: documents/vol3-standard-library/30-char8-t-utf8.md + source_hash: bf65e1fa69d057d8e2387796ce4ed2c2c677e348f2808d359b0b024109c38afc token_count: 1220 + translated_at: '2026-06-14T00:19:58.325857+00:00' --- # char8_t and UTF-8 Strings @@ -112,7 +112,7 @@ int main() { - vector Deep Dive - string Deep Dive - char8_t and UTF-8 + Container Selection Guide + array: Fixed-Length Arrays + Deep Dive into vector + Deep Dive into string + deque, list, and forward_list + Deep Dive into map and set + Deep Dive into unordered_map and set + span: Non-owning View + Container Adapters: stack/queue/priority_queue + New Standard Containers: flat_map/inplace_vector/mdspan + Initializer Lists + Object Size and Trivial Types + Custom Allocators -## Articles to be Rewritten - -The following are early drafts, planned to be rewritten and merged into the main chapter sequence. +## Strings and Text - array (To be rewritten) - initializer_list (To be rewritten) - span (To be rewritten) - Object Size and Trivial Types (To be rewritten) - Custom Allocators (To be rewritten) + char8_t and UTF-8 diff --git a/documents/en/vol4-advanced/01-coroutine-basics.md b/documents/en/vol4-advanced/01-coroutine-basics.md index adc7ce69f..ce76f31d4 100644 --- a/documents/en/vol4-advanced/01-coroutine-basics.md +++ b/documents/en/vol4-advanced/01-coroutine-basics.md @@ -1,20 +1,21 @@ --- -title: Understanding the Revolutionary Features of C++20 — Coroutine Support 1 -description: '' +chapter: 10 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 23 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 10 -order: 8 +title: Understanding the Revolutionary Features of C++20 — Coroutine Support 1 translation: + engine: anthropic source: documents/vol4-advanced/01-coroutine-basics.md source_hash: 1bed23f1e5078d644337bb60c12da6bf7a788ff3ad0d185ebbbc7eb3c1d1b1b0 - translated_at: '2026-05-26T11:39:03.210388+00:00' - engine: anthropic token_count: 5509 + translated_at: '2026-05-26T11:39:03.210388+00:00' +description: '' --- # Understanding the Revolutionary Feature of C++20 — Coroutine Support Part 1 diff --git a/documents/en/vol4-advanced/02-coroutine-scheduler.md b/documents/en/vol4-advanced/02-coroutine-scheduler.md index fb782fcd6..3bc1fba88 100644 --- a/documents/en/vol4-advanced/02-coroutine-scheduler.md +++ b/documents/en/vol4-advanced/02-coroutine-scheduler.md @@ -1,21 +1,22 @@ --- -title: 'Understanding the Revolutionary Features of C++20 — Coroutine Support Part - 2: Writing a Simple Coroutine Scheduler' -description: '' +chapter: 10 +difficulty: intermediate +order: 9 +platform: host +reading_time_minutes: 25 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 10 -order: 9 +title: 'Understanding the Revolutionary Features of C++20 — Coroutine Support Part + 2: Writing a Simple Coroutine Scheduler' translation: + engine: anthropic source: documents/vol4-advanced/02-coroutine-scheduler.md source_hash: a958e4bdda10633048b6eed587a002c22173e7c2b1618a656893cf003d1e2265 - translated_at: '2026-05-26T11:38:46.571919+00:00' - engine: anthropic token_count: 6731 + translated_at: '2026-05-26T11:38:46.571919+00:00' +description: '' --- # Understanding the Revolutionary Features of C++20 — Coroutine Support Part 2: Writing a Simple Coroutine Scheduler diff --git a/documents/en/vol4-advanced/03-empty-base-optimization.md b/documents/en/vol4-advanced/03-empty-base-optimization.md index 04d4da62d..b4627f9cb 100644 --- a/documents/en/vol4-advanced/03-empty-base-optimization.md +++ b/documents/en/vol4-advanced/03-empty-base-optimization.md @@ -11,7 +11,7 @@ order: 6 platform: host prerequisites: - 'Chapter 2: 零开销抽象' -reading_time_minutes: 6 +reading_time_minutes: 5 tags: - host - cpp-modern @@ -19,11 +19,11 @@ tags: - 零开销抽象 title: EBO (Empty Base Optimization) translation: + engine: anthropic source: documents/vol4-advanced/03-empty-base-optimization.md source_hash: 3489c25ee12064211c70c3b43127eeb31d5a3080a8648c62ff6c3f9258fe0ee1 - translated_at: '2026-06-13T11:50:22.052740+00:00' - engine: anthropic token_count: 840 + translated_at: '2026-06-13T11:50:22.052740+00:00' --- # Empty Base Optimization (EBO): A C++ Slimming Technique diff --git a/documents/en/vol4-advanced/05-spaceship-operator.md b/documents/en/vol4-advanced/05-spaceship-operator.md index d9fe648a5..3efa11456 100644 --- a/documents/en/vol4-advanced/05-spaceship-operator.md +++ b/documents/en/vol4-advanced/05-spaceship-operator.md @@ -1,33 +1,33 @@ --- -title: Three-way comparison operator (C++20 Spaceship Operator) -description: 'Detailed Explanation of the C++20 Three-Way Comparison Operator: Simplifying - Comparison Logic for Custom Types' chapter: 11 +cpp_standard: +- 20 +description: 'Detailed explanation of the C++20 three-way comparison operator: simplifying + comparison logic for custom types' +difficulty: intermediate order: 5 +platform: host +prerequisites: +- 'Chapter 11.1: auto与decltype' +- 'Chapter 11.2: 结构化绑定' +reading_time_minutes: 22 tags: - cpp-modern - host - intermediate -difficulty: intermediate -reading_time_minutes: 30 -prerequisites: -- 'Chapter 11.1: auto与decltype' -- 'Chapter 11.2: 结构化绑定' -cpp_standard: -- 20 -platform: host +title: Three-way comparison operator (C++20 Spaceship Operator) translation: - source: documents/vol4-advanced/05-spaceship-operator.md - source_hash: 968dba94e12efb78827b9f24621a35362acdf36dd79af278d44399a7a87cc4f6 - translated_at: '2026-06-13T11:50:40.195404+00:00' engine: anthropic - token_count: 7328 + source: documents/vol4-advanced/05-spaceship-operator.md + source_hash: d1e342cc4a916cbbfcc47ae43a9a40b3b2fd37d7107b2091c5520eb0bb30457b + token_count: 7327 + translated_at: '2026-06-15T09:22:23.372298+00:00' --- # Modern Embedded C++ Development — Three-Way Comparison Operator ## Introduction -Have you ever found yourself frustrated with comparison operators while writing embedded code? +Have you ever found comparison operators to be a headache while writing embedded code? ```cpp class SensorReading { @@ -73,12 +73,12 @@ This is a disaster! To implement a fully sortable type, you need to write six co The **Three-way Comparison Operator** introduced in C++20, commonly known as the **Spaceship Operator** (`<=>`), was designed to solve this problem. -> TL;DR: **The three-way comparison operator automatically generates all six comparison operators with a single definition, significantly simplifying comparison logic for custom types.** +> TL;DR: **The three-way comparison operator defines all six comparison operators with a single definition, drastically simplifying comparison logic for custom types.** In embedded development, this feature is particularly useful: 1. Sorting sensor data by time or priority -2. Firmware version comparison (complex versions with alphanumeric suffixes) +2. Firmware version comparison (complex versions with alphabetic suffixes) 3. Lexicographical comparison of configuration parameters 4. Task sorting in priority queues @@ -91,7 +91,7 @@ In embedded development, this feature is particularly useful: ### Operator Symbol -The three-way comparison operator uses the `<=>` symbol, named for its resemblance to a spaceship: +The three-way comparison operator uses the `<=>` symbol, named so because it looks like a spaceship: ```cpp #include @@ -110,7 +110,7 @@ struct Point { ### Return Value Type -The return value of the three-way comparison operator is not `bool`, but rather a "comparison category" representing the result: +The return value of the three-way comparison operator is not `bool`, but a "comparison category" representing the comparison result: ```cpp // <=> 返回值可以理解为: @@ -151,13 +151,13 @@ int main() { ``` ------ -**Best Practice**: Use `<`, `==`, and `>` directly to judge comparison results instead of calling named methods. This keeps code concise and works for all comparison categories. +**Best Practice**: Use `<`, `==`, and `>` directly to judge the comparison result, rather than calling named methods. This makes the code more concise and applies to all comparison categories. ------ ## Automatic Generation of Comparison Functions -### Automatic Generation using =default +### Using =default for Automatic Generation The simplest usage is to use `= default` to let the compiler automatically generate all comparison operators: @@ -204,7 +204,7 @@ std::sort(sensors.begin(), sensors.end()); ### Comparison Order -The default generated `<=>` performs lexicographical comparison according to the **member declaration order**: +The default generated `<=>` performs lexicographical comparison in **member declaration order**: ```cpp struct Version { @@ -240,7 +240,7 @@ C++20 defines three comparison categories to represent comparison relationships `strong_ordering` represents the strongest comparison relationship with the following properties: 1. **Equivalence implies equality**: `a == b` if and only if all members of `a` and `b` are equal -2. **Substitutability**: If `a == b`, then `f(a) == f(b)` holds for any function `f` +2. **Substitutability**: When `a == b`, `f(a) == f(b)` holds for any function `f` Use cases: Integers, strings, simple value types @@ -276,12 +276,12 @@ static_assert((c <=> a) == std::strong_ordering::greater); ### partial_ordering: Partial Ordering -`partial_ordering` represents cases where values might be "incomparable": +`partial_ordering` indicates that "incomparable" situations may exist: -1. Some values cannot be compared (e.g., `NaN`) +1. Some values may not be comparable (e.g., `NaN`) 2. Equivalence does not imply equality -Use cases: Floating-point numbers (existence of `NaN`), ranges with allowed values +Use cases: Floating-point numbers (existence of `NaN`), ranges with permissible values ```cpp #include @@ -319,7 +319,7 @@ static_assert((a <=> b) == std::partial_ordering::less); ### weak_ordering: Weak Ordering -`weak_ordering` falls between strong and partial ordering: +`weak_ordering` falls between strong ordering and partial ordering: 1. Equivalence does not imply equality (there may be indistinguishable alternative representations) 2. But all values are comparable (no `unordered`) @@ -378,7 +378,7 @@ static_assert(!(s1 == s2)); // 不相等! | `std::weak_ordering::equivalent` | Equivalent | | `std::weak_ordering::greater` | Greater than | -### Choosing Between Comparison Categories +### Choosing Among the Three Comparison Categories ```cpp #include @@ -444,7 +444,7 @@ graph TD ------ -## Embedded Scenarios in Practice +## Real-World Embedded Scenarios ### Scenario 1: Sensor Data Priority Sorting @@ -515,7 +515,7 @@ void message_queue_example() { ### Scenario 2: Firmware Version Comparison -Firmware version numbers may have complex formats, such as alphanumeric suffixes: +Firmware version numbers may have complex formats, such as alphabetic suffixes: ```cpp #include @@ -661,7 +661,7 @@ void config_example() { ### Scenario 4: Sensor Data with NaN -Some sensors might return invalid data (similar to the NaN concept): +Some sensors might return invalid data (similar to the concept of NaN): ```cpp #include @@ -808,9 +808,9 @@ void alarm_system() { ## Custom Three-Way Comparison Implementation -### Manual Multi-Field Comparison +### Manual Implementation of Multi-Field Comparison -When the default lexicographical order doesn't meet requirements, manual implementation is needed: +When the default lexicographical order does not meet requirements, manual implementation is needed: ```cpp #include @@ -875,7 +875,7 @@ struct Task { }; ``` -For C++20, you can implement simple helpers yourself: +For C++20, you can implement a simple helper yourself: ```cpp // C++20比较合成助手 @@ -931,10 +931,10 @@ struct Task { ### Pitfall 1: Default == Does Not Reverse Generate <=> (Generation is One-Way) -A widespread but outdated claim is: "Writing only `<=>` without `==` causes a compilation error." This was briefly true in early C++20 drafts, but was later fixed by **P1185 (Consistent defaulted comparisons, adopted as a C++20 Defect Report)**—the generation relationship between `<=>` and `==` is **one-way**: +A widespread but now outdated claim is: "Writing only `<=>` without `==` causes a compilation error." This was briefly true in early C++20 drafts, but was later fixed by **P1185 (Consistent defaulted comparisons, adopted as a C++20 Defect Report)**—the generation relationship between `<=>` and `==` is **one-way**: -- default `<=>` → The compiler conveniently generates `==`, `!=`, `<`, `>`, `<=`, and `>=`. So writing only `<=>` is fully sufficient; `==` comes "free". -- Conversely, default `==` → Only generates `==` and `!=`, it will not reverse-generate `<=>` or any relational operators. +- default `<=>` → The compiler conveniently generates `==`, `!=`, `<`, `>`, `<=`, and `>=` all together. So writing only `<=>` is perfectly sufficient; `==` comes "for free". +- Conversely, default `==` → Only generates `==` and `!=`; it will not reverse-generate `<=>` or any relational operators. The real pitfall is the latter: You think "I only care about equality, defaulting a `==` is enough," but then someone writes a `a < b` expression, and the compilation blows up—because `==` doesn't come with relational operators. @@ -964,7 +964,7 @@ int main() { } ``` -Tested (Arch Linux WSL, `-std=c++20`; g++ 16.1.1 and clang++ 22.1.6 behavior consistent): +Tested (Arch Linux WSL, `-std=c++20`; g++ 16.1.1 and clang++ 22.1.6 behave consistently): ```text $ g++ -std=c++20 gotcha.cpp -o gotcha && ./gotcha @@ -977,7 +977,7 @@ gotcha.cpp:23:21: error: no match for 'operator<' (operand types are 'HasEqualit | ~ ^ ~ ``` -A one-sentence mnemonic: `<=>` is "upstream", `==` is "downstream"—upstream sends all operators downstream, while downstream only minds its own business. As long as you want any kind of magnitude comparison, you need `<=>`; only defaulting `==` will never get you `<=>`. See cppreference section "[Default comparisons](https://en.cppreference.com/mwiki/index.php?title=cpp/language/default_comparisons)". +A one-sentence mnemonic: `<=>` is "upstream", `==` is "downstream"—upstream sends all operators downstream, while downstream only minds its own business. As long as you want any kind of magnitude comparison, you need `<=>`; defaulting only `==` will never get you `<=>`. See the cppreference section on "[Default comparisons](https://en.cppreference.com/mwiki/index.php?title=cpp/language/default_comparisons)" for details. ### Pitfall 2: Inconsistent Comparison Categories @@ -1057,7 +1057,7 @@ DerivedWithNew d2{1, 2}; // bool cmp = (d1 == d2); // 编译错误!类型不同 ``` -### Pitfall 4: The Floating-Point NaN Problem +### Pitfall 4: The NaN Problem with Floating-Point Numbers Floating-point `NaN` (Not a Number) causes comparison results to be `unordered`: @@ -1258,28 +1258,28 @@ Experience C++20's three-way comparison operator default generation, custom vers -Let's look back one more time: The three-way comparison operator is an important feature introduced in C++20 that significantly simplifies comparison logic for custom types: +Looking back, the three-way comparison operator is an important feature introduced in C++20 that drastically simplifies comparison logic for custom types: **Core Concepts**: | Concept | Description | |-----|------| -| `<=>` operator | Three-way comparison operator, defines once to auto-generate all six comparison operators | -| Comparison Categories | `strong_ordering`, `weak_ordering`, `partial_ordering` | +| `<=>` operator | Three-way comparison operator; defines all six comparison operators with a single definition | +| Comparison categories | `strong_ordering`, `weak_ordering`, `partial_ordering` | | `= default` | Let the compiler automatically generate comparison logic | -| Comparison Order | Default lexicographical comparison by member declaration order | +| Comparison order | Defaults to lexicographical comparison based on member declaration order | **Comparison Category Selection**: | Category | Characteristics | Use Cases | |-----|------|---------| | `strong_ordering` | Equivalence implies equality | Integers, enums, simple value types | -| `weak_ordering` | Equivalence does not imply equality | Case-insensitive strings, comparisons ignoring fields | +| `weak_ordering` | Equivalence does not imply equality | Case-insensitive strings, comparisons ignoring partial fields | | `partial_ordering` | Possibly incomparable | Floating-point numbers (NaN) | -The three-way comparison operator makes C++ comparison logic more concise and safe. Combined with previously learned features like auto, structured binding, and attributes, modern C++ has evolved into a powerful and expressive system programming language. In embedded development, rational use of these features can make code clearer and easier to maintain. +The three-way comparison operator makes C++ comparison logic more concise and safe. Combined with previously learned features like auto, structured bindings, and attributes, modern C++ has evolved into a powerful and expressive system programming language. In embedded development, using these features appropriately makes code clearer and easier to maintain. diff --git a/documents/en/vol4-advanced/msvc-cpp-modules.md b/documents/en/vol4-advanced/msvc-cpp-modules.md index 72938b3c9..6618f75b1 100644 --- a/documents/en/vol4-advanced/msvc-cpp-modules.md +++ b/documents/en/vol4-advanced/msvc-cpp-modules.md @@ -1,21 +1,22 @@ --- -title: 'Understanding MSVC C++ Modules in One Article: Principles, Motivations, and - Engineering Practices' -description: '' +chapter: 11 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 8 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 11 -order: 8 +title: 'Understanding MSVC C++ Modules in One Article: Principles, Motivations, and + Engineering Practices' translation: + engine: anthropic source: documents/vol4-advanced/msvc-cpp-modules.md source_hash: 74e75bca1d633acf4bdb1479b00dd46b5c104dda06e8b75af8043848d615024d - translated_at: '2026-05-26T11:39:57.001588+00:00' - engine: anthropic token_count: 1178 + translated_at: '2026-05-26T11:39:57.001588+00:00' +description: '' --- # Understanding MSVC C++ Modules: Principles, Motivation, and Engineering Practice diff --git a/documents/en/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md b/documents/en/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md index 9fe5a7ca0..11597ae75 100644 --- a/documents/en/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md +++ b/documents/en/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md @@ -1,26 +1,26 @@ --- -title: designated initializer -description: A Detailed Guide to Modern C++ Designated Initializers and Embedded Applications chapter: 11 +cpp_standard: +- 20 +description: A Detailed Guide to Modern C++ Designated Initializers and Embedded Applications +difficulty: intermediate order: 6 +platform: host +prerequisites: +- 'Chapter 11.1: auto与decltype' +- 'Chapter 11.2: 结构化绑定' +reading_time_minutes: 14 tags: - cpp-modern - host - intermediate -difficulty: intermediate -reading_time_minutes: 30 -prerequisites: -- 'Chapter 11.1: auto与decltype' -- 'Chapter 11.2: 结构化绑定' -cpp_standard: -- 20 -platform: host +title: designated initializer translation: + engine: anthropic source: documents/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md source_hash: ca599e190c8d7c1554ecd34fcb3c3316cbc301c74ec52b75609537dbcacc9fd6 - translated_at: '2026-05-26T11:39:42.384508+00:00' - engine: anthropic token_count: 4251 + translated_at: '2026-05-26T11:39:42.384508+00:00' --- # Modern C++ for Embedded Development—Designated Initializers diff --git a/documents/en/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md b/documents/en/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md index 0d748cc65..5a2ab83c3 100644 --- a/documents/en/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md +++ b/documents/en/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md @@ -11,18 +11,18 @@ order: 7 platform: host prerequisites: - 'Chapter 8: 类型安全' -reading_time_minutes: 16 +reading_time_minutes: 11 tags: - cpp-modern - host - intermediate title: C++20 Range Library Basics and Views translation: + engine: anthropic source: documents/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md source_hash: f8bc594ee9cbcf6b907f60feb2f458d2d3412646523f21596a4f50246d4a1cbb - translated_at: '2026-05-26T11:40:13.208481+00:00' - engine: anthropic token_count: 2581 + translated_at: '2026-05-26T11:40:13.208481+00:00' --- # Modern Embedded C++ Tutorial — C++20 Ranges Library Basics and Views diff --git a/documents/en/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md b/documents/en/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md index c01ad7b15..ef77a4f80 100644 --- a/documents/en/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md +++ b/documents/en/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md @@ -11,18 +11,18 @@ order: 8 platform: host prerequisites: - 'Chapter 8: 类型安全' -reading_time_minutes: 19 +reading_time_minutes: 12 tags: - cpp-modern - host - intermediate title: Pipes and Ranges in Practice translation: + engine: anthropic source: documents/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md source_hash: 41896798a70b054936dbc6dc2d4bfe1de7d006d7aa04eb93a76c08dd9751276a - translated_at: '2026-05-26T11:41:06.812417+00:00' - engine: anthropic token_count: 3136 + translated_at: '2026-05-26T11:41:06.812417+00:00' --- # Modern Embedded C++ Tutorial — Pipeline Operations and Ranges in Practice diff --git a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/01-why-concurrency.md b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/01-why-concurrency.md index 35077fd18..ab65f0036 100644 --- a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/01-why-concurrency.md +++ b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/01-why-concurrency.md @@ -1,32 +1,32 @@ --- -title: Why We Need Concurrency +chapter: 0 +cpp_standard: +- 11 +- 17 +- 20 description: Understand the difference between concurrency and parallelism, master Amdahl's Law and Gustafson's Law, and build the engineering judgment to know when to introduce concurrency. -chapter: 0 +difficulty: beginner order: 1 +platform: host +reading_time_minutes: 11 +related: +- 并发基本问题 +- std::thread 基础 tags: - host - cpp-modern - beginner - 基础 - 入门 -difficulty: beginner -platform: host -reading_time_minutes: 12 -cpp_standard: -- 11 -- 17 -- 20 -related: -- 并发基本问题 -- std::thread 基础 +title: Why We Need Concurrency translation: + engine: anthropic source: documents/vol5-concurrency/ch00-concurrency-fundamentals/01-why-concurrency.md source_hash: 3d5a79225b0f2761b5ea7403f304f71da6e9ac2f90b4967aaf24e430486e4a79 - translated_at: '2026-05-20T04:34:29.186546+00:00' - engine: anthropic token_count: 1349 + translated_at: '2026-05-20T04:34:29.186546+00:00' --- # Why We Need Concurrency diff --git a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md index 9d9af3a74..e726274af 100644 --- a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md +++ b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md @@ -1,33 +1,33 @@ --- -title: Fundamental Concurrency Issues -description: 'Identify the most common bugs in concurrent programming: data race, - race condition, dead lock, livelock, starvation, and priority inversion.' chapter: 0 -order: 2 -tags: -- host -- cpp-modern -- beginner -- atomic -- mutex -difficulty: beginner -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 17 - 20 +description: 'Identify the most common bugs in concurrent programming: data race, + race condition, dead lock, livelock, starvation, and priority inversion.' +difficulty: beginner +order: 2 +platform: host prerequisites: - 为什么需要并发 +reading_time_minutes: 16 related: - mutex 与 RAII 守卫 - std::atomic 原子操作 +tags: +- host +- cpp-modern +- beginner +- atomic +- mutex +title: Fundamental Concurrency Issues translation: + engine: anthropic source: documents/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md source_hash: d336eb3333a93d0c5756f6d12e6aaa776b0bd2dfa36ea281f9e3cf2d2cde736a - translated_at: '2026-05-20T04:31:55.772461+00:00' - engine: anthropic token_count: 2616 + translated_at: '2026-05-20T04:31:55.772461+00:00' --- # Fundamental Concurrency Problems diff --git a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md index 55f96debc..b72151826 100644 --- a/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md +++ b/documents/en/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md @@ -1,34 +1,34 @@ --- -title: CPU Cache and OS Threads -description: From the hardware cache hierarchy to the OS thread model, understanding - the real physical stage where multithreaded programs execute. chapter: 0 -order: 3 -tags: -- host -- cpp-modern -- intermediate -- 基础 -- atomic -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 17 - 20 +description: From the hardware cache hierarchy to the OS thread model, understanding + the real physical stage where multithreaded programs execute. +difficulty: intermediate +order: 3 +platform: host prerequisites: - 为什么需要并发 - 并发基本问题 +reading_time_minutes: 27 related: - std::thread 基础 - 原子操作模式 +tags: +- host +- cpp-modern +- intermediate +- 基础 +- atomic +title: CPU Cache and OS Threads translation: + engine: anthropic source: documents/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md source_hash: 94c702129dc64029311ea000163119052a11506433a9c538142e54859c958552 - translated_at: '2026-05-20T04:34:28.797438+00:00' - engine: anthropic token_count: 3825 + translated_at: '2026-05-20T04:34:28.797438+00:00' --- # CPU Cache and OS Threads diff --git a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md index 7a3478565..510f02403 100644 --- a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md +++ b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md @@ -1,507 +1,303 @@ --- -title: std::thread Basics -description: Master C++ thread creation, join, detach, ID, and hardware concurrency - queries to build intuition for your first multithreaded program. chapter: 1 -order: 1 -tags: -- host -- cpp-modern -- beginner -- 入门 -difficulty: beginner -platform: host -reading_time_minutes: 20 cpp_standard: - 11 - 14 - 17 +description: Master C++ thread creation, joining, detaching, IDs, and hardware concurrency + queries, and build intuition for your first multithreaded program. +difficulty: beginner +order: 1 +platform: host prerequisites: - CPU cache 与 OS 线程 +reading_time_minutes: 15 related: - 线程参数与生命周期 - 线程所有权与 RAII +tags: +- host +- cpp-modern +- beginner +- 入门 +title: std::thread Basics translation: - source: documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md - source_hash: 3e54746c8ee17ebdb124c6e9ed898f5e218cc707a34e0331855ed14af2983f15 - translated_at: '2026-05-26T11:42:44.815282+00:00' engine: anthropic - token_count: 3678 + source: documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md + source_hash: 3c0b3d21b5ed102e2c133bd511b483dc03343df1fe9967f874496d7c911908d4 + token_count: 3677 + translated_at: '2026-06-15T09:23:40.757337+00:00' --- # std::thread Basics -In the previous chapter, we discussed the CPU cache hierarchy, the MESI protocol, false sharing, and also looked at the Linux threading model and the futex mechanism — these form the physical stage on which multithreaded programs run. But knowing what the stage looks like isn't enough; we need to step onto it ourselves. This article marks our first time on stage: starting from the construction of `std::thread`, we'll figure out how to create a thread, how to wait for it, how to "let it go," and what pitfalls we might stumble into along the way. +In the previous chapter, we discussed the CPU cache hierarchy, the MESI protocol, and false sharing, as well as Linux's threading model and the futex mechanism—these constitute the physical stage where multithreaded programs run. But knowing what the stage looks like isn't enough; we need to get on it and perform. This post marks our first debut: starting with the construction of `std::thread`, we will figure out how to create threads, how to wait for them, how to "let them go," and what pitfalls we might stumble into during these operations. -`std::thread` is the standard thread class introduced in C++11, defined in the `` header. It is the C++ standard library's direct wrapper around OS threads — on Linux, behind every `std::thread` object lies a pthread, and that pthread is mapped to a kernel scheduling entity via the `clone()` system call. The 1:1 model we mentioned in the previous article is exactly what's at work here. +`std::thread` is the standard thread class introduced in C++11, defined in the `` header file. It is a direct wrapper of the operating system threads by the C++ Standard Library—on Linux, behind every `std::thread` object lies a pthread, which is mapped to a kernel scheduling entity via the `clone` system call. The 1:1 model we mentioned in the last post is exactly embodied here. -## Three Ways to Construct a std::thread +## Constructing std::thread in Three Ways -The constructor of `std::thread` accepts a **callable** and an optional list of arguments. C++ provides several ways to express a "callable," so let's look at them one by one. +The `std::thread` constructor accepts a **callable object** and an optional list of arguments. C++ provides us with several ways to express "callable," so let's examine them one by one. -### Function Pointers +### Function Pointer -The most straightforward approach is to pass a plain function pointer: +The most straightforward way is to pass a plain function pointer: ```cpp -#include -#include - -void print_hello(int id) -{ - std::cout << "Hello from thread " << id << "\n"; +void task(int n) { + printf("Task %d running\n", n); } -int main() -{ - std::thread t(print_hello, 42); - t.join(); - return 0; -} +std::thread t(task, 42); // Pass function pointer and arguments ``` -`std::thread t(print_hello, 42)` does a few things: first, it packages `print_hello` (the function pointer) and `42` (the arguments) into internal storage; then, it calls the underlying `pthread_create` (or equivalent system call) to create a new OS thread; finally, the new thread calls `print_hello(42)` with the saved arguments in that independent execution context. Note that the argument `42` is **copied** into the thread's internal storage — we'll dive into the details of argument passing in the next article. +`std::thread` does a few things here: first, it packs `task` (the function pointer) and `42` (the argument) into internal storage; then, it calls the underlying `pthread_create` (or an equivalent system call) to create a new operating system thread; finally, the new thread calls `task` with the saved arguments in that independent execution context. Note that the argument `42` is **copied** into the thread's internal storage—we will dive into the details of argument passing in the next post. -### Lambda Expressions +### Lambda Expression -In real-world engineering, lambdas are the most common way to create threads because they let you define the thread's task right at the call site without declaring a separate function: +In actual engineering, lambdas are the most common way to create threads because they allow you to define what the thread does directly at the call site without declaring an extra function: ```cpp -#include -#include -#include - -int main() -{ - std::vector data = {1, 2, 3, 4, 5}; - int sum = 0; - - std::thread t([&data, &sum]() { - for (int v : data) { - sum += v; - } - }); - - t.join(); - std::cout << "Sum = " << sum << "\n"; - return 0; -} +int data = 10; +std::thread t([&]() { + // Capture 'data' by reference + data += 5; +}); ``` -This code works fine, but if you look closely, `[&data, &sum]` is captured by reference — this is perfectly fine in a single-threaded scenario, but what if the thread is detached or its lifetime extends beyond the scope of `data` and `sum`? That's a breeding ground for dangling references. Let's keep this "smell" in mind; we'll systematically dissect it in the next article. +This code works, but if you look closely, `data` is captured by reference—while this is perfectly fine in a single-threaded context, what if the thread is detached or its lifetime exceeds the scope of `data`? This becomes a breeding ground for dangling references. Let's keep this "smell" in mind; we will systematically dissect it in the next post. -### Function Objects (Functors) +### Functor -The third approach is to pass a class instance that overloads `operator()`: +The third way is to pass a class instance that overloads `operator()`: ```cpp -#include -#include -#include - -class Accumulator { -public: - Accumulator(const std::vector& data, int& result) - : data_(data), result_(result) - {} - - void operator()() const - { - int local_sum = 0; - for (int v : data_) { - local_sum += v; - } - result_ = local_sum; +struct Worker { + void operator()() { + printf("Working...\n"); } - -private: - const std::vector& data_; // 注意:引用成员 - int& result_; // 引用成员 }; -int main() -{ - std::vector data = {1, 2, 3, 4, 5}; - int result = 0; - - // 注意:这里需要用花括号或 lambda 避免最令人头疼的解析问题 - // std::thread t(Accumulator(data, result)); // 编译错误!被解析为函数声明 - Accumulator acc(data, result); - std::thread t(acc); // OK:拷贝 acc 到线程中 - - t.join(); - std::cout << "Result = " << result << "\n"; - return 0; -} +Worker w; +std::thread t(w); // Pass the functor object ``` -There's a classic C++ trap here — if you write `std::thread t(Accumulator(data, result));` directly, the compiler will parse it as a function declaration named `t` (with a parameter type of pointer to `Accumulator`), rather than the definition of a thread object. This is the so-called "most vexing parse" problem. There are several ways to solve it: use extra braces `std::thread t{Accumulator(data, result)};`, use a lambda `std::thread t([&](){ ... });`, or construct a named object first and pass it in, as shown above. +Here is a classic C++ trap—if you write `std::thread t(Worker());` directly, the compiler will parse it as a function declaration named `t` (with a parameter type that is a pointer to `Worker`), rather than a definition of a thread object. This is known as the "most vexing parse" problem. There are several solutions: use extra braces `std::thread t{Worker()};`, use a lambda `std::thread t([]{ Worker()(); });`, or construct a named object first and pass it in, as shown above. -Each approach has its own use cases. Function pointers suit simple, stateless thread functions; lambdas suit defining local logic at the call site and are the most common approach in day-to-day development; functors suit complex tasks that need to carry state — but watch out for the lifetime risks introduced by reference members. In real projects, lambdas cover over 90% of use cases. +Each method has its use cases. Function pointers suit simple, stateless thread functions; lambdas suit defining local logic at the call site and are the most common approach in daily development; functors suit complex tasks that need to carry state—but beware of the lifetime risks introduced by reference members. In real projects, lambdas cover more than 90% of scenarios. ## join() vs detach(): Two Radically Different Strategies -After creating a thread, we must make a decision before its lifetime ends: **join** or **detach**. This decision directly affects the correctness of the program. +Once a thread is created, we must make a decision before its lifetime ends: **join** or **detach**. This decision directly affects the correctness of the program. -### join: Waiting for a Thread to Finish +### join: Waiting for the Thread to Finish -`join()` is a blocking call — the current thread stops right there and waits until the target thread finishes executing before continuing. An analogy: you send someone to do a job, you stand there and wait until they're done, and then you both move on together. This is the most common pattern, and also the safest. +`join()` is a blocking call—the current thread stops there and waits for the target thread to finish execution before continuing. The analogy is: you send someone to do a job, you stand there and wait until they are done, and then you continue together. This is the most common and safest pattern. ```cpp -#include -#include -#include - -void slow_work() -{ - std::cout << "Worker: starting...\n"; - std::this_thread::sleep_for(std::chrono::seconds(2)); - std::cout << "Worker: done.\n"; +void worker() { + printf("Worker started\n"); + std::this_thread::sleep_for(std::chrono::seconds(1)); + printf("Worker finished\n"); } -int main() -{ - std::cout << "Main: launching thread\n"; - std::thread t(slow_work); - std::cout << "Main: waiting for thread...\n"; - t.join(); - std::cout << "Main: thread finished, continuing\n"; - return 0; +int main() { + printf("Main start\n"); + std::thread t(worker); + t.join(); // Block until worker finishes + printf("Main continues\n"); } ``` -When you run this code, you'll see the output happen in a strict order: Main starts -> Worker starts -> Worker finished -> Main continues. `join()` guarantees that the thread's execution results are visible to the calling thread when `join` returns — this is a happens-before relationship. +Running this code, you will see the output strictly in the order: Main start -> Worker started -> Worker finished -> Main continues. `join()` guarantees that the thread's execution results are visible to the calling thread when `join()` returns—this is a happens-before relationship. -### detach: Letting It Go +### detach: Letting Go -`detach()` does the exact opposite — it "detaches" the thread from the `std::thread` object's management. After detachment, the thread runs independently in the background (a so-called daemon thread / background thread), and the `std::thread` object no longer holds any reference to it. You can't join it anymore either — the `joinable()` of the `std::thread` object will return `false`. +`detach()` does the exact opposite—it "detaches" the thread from the management of the `std::thread` object. After detaching, the thread runs independently in the background (a so-called daemon thread/background thread), and the `std::thread` object no longer holds any reference to it. You can't join it anymore—the `joinable()` method of the `std::thread` object will return `false`. ```cpp -#include -#include -#include - -void background_task() -{ - std::this_thread::sleep_for(std::chrono::seconds(2)); - std::cout << "Background task finished\n"; -} - -int main() -{ - std::thread t(background_task); - t.detach(); +int main() { + std::thread t([]() { + std::this_thread::sleep_for(std::chrono::seconds(2)); + printf("Background task finished\n"); + }); - std::cout << "Main: detached thread, sleeping 1 second...\n"; + t.detach(); // Detach from the object + printf("Main thread exiting soon...\n"); std::this_thread::sleep_for(std::chrono::seconds(1)); - std::cout << "Main: exiting\n"; return 0; } ``` -If you run this code, you'll most likely not see the "Background task finished" output — because the main thread waits only one second before exiting, while the detached thread needs two seconds. When a process exits, all threads (including detached ones) are forcibly terminated with no chance to clean up. This is the biggest risk of detach: **you completely lose control over the thread's execution timing**. +If you run this code, you likely won't see the "Background task finished" line—because the main thread waits only one second before exiting, while the detached thread needs two. When the process exits, all threads (including detached ones) are forcibly terminated without any chance for cleanup. This is the biggest risk of `detach`: **you completely lose control over the thread's execution timing**. -So when should you use detach? Honestly, in most application code, detach is not a good choice. The scenarios where it fits are very limited — for example, a background logging thread whose job is to flush logs from an in-memory buffer to disk, where you don't care when it finishes as long as it eventually writes the data out. But even in this scenario, using a `joinable` thread with an explicit shutdown signal is usually a more robust approach. +So when should you use `detach`? Honestly, in most application code, `detach` is not a good choice. Its suitable scenarios are very limited—such as a background logging thread whose job is to flush logs from a memory buffer to disk. You don't care when it finishes, as long as it eventually writes the data out. But even in this scenario, using a `joinable` thread with an explicit shutdown signal is usually a safer approach. -### The Consequence of Neither join nor detach: std::terminate +### The Consequence of Neither Joining nor Detaching: std::terminate -If you have a `std::thread` `joinable` object and you neither call `join()` nor call `detach()`, letting it naturally reach its destructor — your program will call `std::terminate()` and crash outright. This isn't a suggestion; it's a hard requirement mandated by the standard: +If you let a `joinable` `std::thread` object's destructor run without calling `join()` or `detach()`, your program will call `std::terminate()` and crash immediately. This isn't a suggestion; it's a hard requirement mandated by the standard: ```cpp -#include -#include - -void some_work() -{ - std::cout << "Working...\n"; -} - -int main() -{ - std::thread t(some_work); - // 没有 join() 也没有 detach() - // t 析构时调用 std::terminate() - return 0; // terminate called without an active exception +int main() { + std::thread t([]() { + printf("Running...\n"); + }); + // Forgot join/detach -> std::terminate called here + return 0; } ``` -There's a good reason the C++ standard is designed this way. If the destructor silently joined for you, destruction could block — something many developers don't want to accept (destructors should be fast). If the destructor silently detached for you, the thread might access references to objects that no longer exist after destruction — that's undefined behavior (UB), which is worse than a crash. The standard chooses to call `terminate` directly, forcing you to **explicitly make a decision**: either wait for it to finish (join) or let it go (detach), but you can't pretend this problem doesn't exist. +The C++ standard is designed this way for a reason. If the destructor silently joined for you, destruction might block—which many developers don't want (destructors should be fast). If the destructor silently detached for you, the thread might access references that no longer exist after the object is destroyed—that is undefined behavior, which is worse than a crash. The standard chooses to call `std::terminate` immediately to force you to **make an explicit decision**: either wait for it to finish (join) or let it go (detach), but you can't pretend this problem doesn't exist. -This design philosophy permeates the entire C++ concurrency API: don't do implicit, surprising things; leave the decision to the programmer. The price is that you must remember to handle join/detach on every code path, including exception paths. A common pattern is to use an RAII wrapper — save the thread on construction, and automatically join on destruction — a topic we'll expand upon later in this chapter. +This design philosophy runs through the entire C++ Concurrency API: do nothing implicit or surprising, and give the decision power to the programmer. The cost is that you must remember to handle the thread's join/detach on every code path, including exception paths. A common pattern is to use an RAII wrapper—save the thread on construction, and automatically join on destruction—we will expand on this topic later in this chapter. ## Thread Identification and Queries -### get_id(): A Thread's ID Number +### get_id(): The Thread's ID Number -Every thread has a unique identifier of type `std::thread::id`. You can get a thread object's ID via `std::thread::get_id()`, or get the current thread's ID via `std::this_thread::get_id()`. `std::thread::id` supports comparison operations and output to `std::ostream`, making it convenient for debugging and logging: +Every thread has a unique identifier of type `std::thread::id`. You can get a thread object's ID via `get_id()`, or get the current thread's ID via `std::this_thread::get_id()`. `std::thread::id` supports comparison operations and output to `std::ostream`, which is convenient for debugging and logging: ```cpp -#include -#include - -void worker() -{ - std::cout << "Worker thread ID: " - << std::this_thread::get_id() << "\n"; -} +std::thread t([]() { + printf("Worker ID: %s\n", + std::this_thread::get_id().operator std::string().c_str()); // Simplified for demo +}); -int main() -{ - std::thread t(worker); - std::cout << "Main thread ID: " - << std::this_thread::get_id() << "\n"; - std::cout << "Worker's thread ID (from main): " - << t.get_id() << "\n"; - t.join(); - - // join 或 detach 后,get_id() 返回默认构造的 id - std::cout << "After join, worker ID: " - << t.get_id() << "\n"; - return 0; -} +printf("Main ID: %s\n", + std::this_thread::get_id().operator std::string().c_str()); ``` -A few things to note: the specific value of `std::thread::id` is implementation-defined — different compilers and platforms may output different formats (GCC usually outputs a number, MSVC might output a hexadecimal address), so don't rely on its specific format for logic decisions. After `join()` or `detach()`, `get_id()` returns a default-constructed `std::thread::id{}`, meaning "not associated with any thread" — this is the same return value as `get_id()` on a default-constructed `std::thread` object. +A few things to note: the specific value of `std::thread::id` is implementation-defined—different compilers and platforms may output different formats (GCC usually outputs a number, MSVC might output a hexadecimal address), so don't rely on its specific format for logic checks. After `join()` or `detach()`, `get_id()` returns a default-constructed `std::thread::id`, indicating "no associated thread"—this is the same as the return value of `get_id()` on a default-constructed `std::thread` object. -The most practical use case for `thread::id` is as a key in a `std::hash` to allocate per-thread resources (such as a separate memory pool or log buffer for each thread). You can also use it to detect whether the "current thread is the main thread," implementing simple thread-safe assertions. +The most practical use for `std::thread::id` is as a key in a `std::map` to allocate resources for threads (e.g., a separate memory pool or log buffer per thread). It can also be used to detect if the "current thread is the main thread," implementing simple thread-safe assertions. ### native_handle(): Touching the OS Native Handle -`std::thread` is a standard library abstraction, but sometimes you need to manipulate the underlying OS thread directly — for example, to set thread priority, CPU affinity, or the thread name. `native_handle()` returns a platform-dependent native thread handle: on Linux it's a `pthread_t`, and on Windows it's a `HANDLE`. +`std::thread` is a Standard Library abstraction, but sometimes you need to manipulate the underlying operating system thread directly—such as setting thread priority, CPU affinity, or the thread name. `native_handle()` returns a platform-specific native thread handle: `pthread_t` on Linux, `HANDLE` on Windows. ```cpp -#include -#include - -// 注意:以下代码是 Linux 专用的 -#ifndef _WIN32 -#include -#include -#endif - -void set_high_priority(std::thread& t) -{ -#ifndef _WIN32 - sched_param param; - param.sched_priority = 10; // 较高的优先级(具体值取决于调度策略) - pthread_setschedparam(t.native_handle(), SCHED_RR, ¶m); -#endif -} - -int main() -{ - std::thread t([]() { - std::cout << "High priority thread running\n"; - }); - set_high_priority(t); - t.join(); - return 0; -} +std::thread t([]() {}); +pthread_t native_t = t.native_handle(); // Linux specific +// Set thread priority... ``` -This code is obviously non-portable — it will only compile on platforms that support pthread. In real projects, platform-specific code is usually isolated using `#ifdef`, or abstracted into a platform layer. `native_handle()` gives you an "escape hatch," letting you deal directly with the OS when the standard library isn't enough. +This code is clearly non-portable—it will only compile on platforms supporting pthread. In actual projects, platform-specific code is usually isolated with `#ifdef` or abstracted into a platform layer. `native_handle()` gives you an "escape hatch" to deal directly with the operating system when the Standard Library isn't enough. ### hardware_concurrency(): How Many Cores Do I Have -`std::thread::hardware_concurrency()` is a static member function that returns a hint indicating the number of threads that can truly execute concurrently on the current system — in most cases, this is the number of logical CPU cores (including hyperthreading). +`hardware_concurrency()` is a static member function that returns a hint indicating the number of threads that can truly run concurrently on the current system—in most cases, this is the number of logical CPU cores (including hyperthreading). ```cpp -#include -#include - -int main() -{ - unsigned int cores = std::thread::hardware_concurrency(); - std::cout << "Hardware concurrency: " << cores << "\n"; - return 0; -} +unsigned int cores = std::thread::hardware_concurrency(); +printf("Concurrent threads supported: %u\n", cores); ``` -This value is a hint, not a guarantee. If the information is unavailable, the function returns 0. On an 8-core, 16-thread CPU, it typically returns 16. In container environments, it might return the number of CPU cores allocated to the container rather than the total cores of the physical machine. The most common use is to decide the size of a thread pool or the number of task partitions based on it — but don't treat it as an exact value; it's best to check whether the return value is 0 before using it. +This value is a hint, not a guarantee. If the information is unavailable, the function returns 0. On an 8-core, 16-thread CPU, it usually returns 16. In container environments, it might return the number of cores allocated to the container rather than the physical machine's total. The most common use is to decide the size of a thread pool or the number of task shards—but don't treat it as an exact value; it's best to check if the return value is 0 before using it. ## Exceptions in Thread Functions -There is a very important rule here: **exceptions should never escape a thread function**. If an exception escapes from a thread function (i.e., the thread function throws an exception but it isn't caught inside the thread), `std::terminate()` will be called and the program will crash outright. +Here is a very important rule: **exceptions should never escape a thread function**. If an exception escapes from a thread function (i.e., the thread function throws an exception but it isn't caught inside the thread), `std::terminate` is called, and the program crashes immediately. ```cpp -#include -#include -#include - -void unsafe_worker() -{ - throw std::runtime_error("Oops, something went wrong!"); - // 异常逃逸线程函数 -> std::terminate() +void risky_task() { + throw std::runtime_error("Oops!"); } -int main() -{ - try { - std::thread t(unsafe_worker); - t.join(); // 永远到不了这里 - } catch (const std::exception& e) { - // 这个 catch 捕获不到线程里的异常! - // 线程函数中的异常和主线程的 try-catch 是完全隔离的 - std::cout << "Caught: " << e.what() << "\n"; - } +int main() { + std::thread t(risky_task); + t.join(); // std::terminate called inside join return 0; } ``` -This behavior actually makes sense. Each thread has its own independent call stack, and the exception handling mechanism (stack unwinding, catch matching) only works on the current thread's stack. If an exception pierces through the thread function, it means there's no catch block to receive it — except for `std::terminate`. The main thread's `try-catch` and the child thread's exception handling are two completely isolated worlds. +This behavior is actually quite reasonable. Each thread has its own independent call stack, and the exception handling mechanism (stack unwinding, catch matching) only works on the current thread's stack. If an exception pierces through the thread function, it means no catch block can catch it—except `std::terminate`. The main thread's `try-catch` and the child thread's exception handling are two completely isolated worlds. -The correct approach is to handle all possible exceptions inside the thread function, or pass the exception information back to the caller through some mechanism (`std::promise`/`std::future`, `std::exception_ptr`). A simplest defensive pattern looks like this: +The correct approach is to handle all possible exceptions inside the thread function, or pass exception information back to the caller via some mechanism (`std::exception_ptr`/`std::current_exception`, `std::promise`). A simple defensive pattern looks like this: ```cpp -#include -#include -#include -#include - -void safe_worker(std::function task) -{ +std::thread t([]() { try { - task(); + // Do work } catch (const std::exception& e) { - // 在线程内部处理异常,或者记录下来 - std::cerr << "Thread caught exception: " - << e.what() << "\n"; - } catch (...) { - std::cerr << "Thread caught unknown exception\n"; + // Log or store error } -} - -int main() -{ - std::thread t(safe_worker, []() { - throw std::runtime_error("Oops!"); - }); - t.join(); // OK:异常在线程内部被捕获,程序不会 terminate - std::cout << "Main continues normally\n"; - return 0; -} +}); ``` -In later chapters, we'll introduce `std::async` and `std::promise`/`std::future`, which provide more elegant ways to propagate child thread exceptions back to the main thread. But in scenarios where we use `std::thread` directly, the "catch-all inside the thread" pattern above is the most basic defensive measure. +In later chapters, we will introduce `std::exception_ptr` and `std::promise`/`std::future`, which provide more elegant ways to pass child thread exceptions back to the main thread. But in scenarios using `std::thread` directly, this "catch-all inside the thread" pattern is the most basic defensive measure. ## Basic Pattern: Spawn Threads, Join on Scope Exit With the knowledge above, we can summarize a most basic thread usage pattern: spawn a thread for each subtask, and join all threads before the current scope exits. Expressed in code: ```cpp -#include -#include -#include -#include -#include - -void process_range(const std::vector& input, - std::vector& output, - std::size_t start, - std::size_t end) -{ - for (std::size_t i = start; i < end; ++i) { - // 模拟一个计算密集型操作 - output[i] = input[i] * input[i]; - } +std::vector data(1000); +std::vector threads; +const int thread_count = 4; +const int chunk_size = data.size() / thread_count; + +for (int i = 0; i < thread_count; ++i) { + threads.emplace_back([&, i] { // Capture by reference, capture i by value + int start = i * chunk_size; + int end = (i == thread_count - 1) ? data.size() : (start + chunk_size); + for (int j = start; j < end; ++j) { + data[j] *= 2; + } + }); } -int main() -{ - constexpr std::size_t kDataSize = 10'000'000; - constexpr unsigned int kNumThreads = 4; - - std::vector input(kDataSize); - std::vector output(kDataSize); - - // 初始化输入数据 - for (std::size_t i = 0; i < kDataSize; ++i) { - input[i] = static_cast(i); - } - - auto start_time = std::chrono::high_resolution_clock::now(); - - std::vector threads; - threads.reserve(kNumThreads); - - std::size_t chunk_size = kDataSize / kNumThreads; - - // 派生线程 - for (unsigned int i = 0; i < kNumThreads; ++i) { - std::size_t start = i * chunk_size; - std::size_t end = (i == kNumThreads - 1) - ? kDataSize - : start + chunk_size; - threads.emplace_back(process_range, - std::cref(input), - std::ref(output), - start, - end); - } - - // 在作用域退出前 join 所有线程 - for (auto& t : threads) { - t.join(); - } - - auto end_time = std::chrono::high_resolution_clock::now(); - auto ms = std::chrono::duration_cast( - end_time - start_time); - - std::cout << "Processed " << kDataSize << " elements in " - << ms.count() << " ms using " - << kNumThreads << " threads\n"; - return 0; +for (auto& t : threads) { + t.join(); } ``` -The execution flow of this code is clear: split the data into N chunks, hand each chunk to a thread for processing, and then the main thread waits for all worker threads to finish. `threads.emplace_back(...)` constructs the thread objects directly in the vector, avoiding extra moves. The final for loop joins them one by one, ensuring all threads have finished executing before exiting. +The execution flow of this code is clear: split the data into N parts, hand each part to a thread for processing, and the main thread waits for all worker threads to finish. `emplace_back` constructs the thread object directly in the vector, avoiding extra moves. The final for loop joins one by one, ensuring all threads have finished execution before exiting. -There's a detail worth noting here: `output` is passed by reference into each thread (via `std::ref`), but different threads write to different ranges of `output` — there's no overlap, so no data race occurs. This "partitioned parallelism" pattern is one of the easiest ways to write correct code in multithreaded programming: as long as you ensure each thread only touches its own portion of data, you don't need any synchronization mechanism. +There is a detail worth noting: `data` is passed into each thread by reference (via `&`), but different threads write to different ranges of `data`—no overlap, so no data race occurs. This "partitioned parallelism" pattern is one of the easiest ways to write correct code in multithreaded programming: as long as you ensure each thread only touches its own share of data, you don't need any synchronization mechanisms. -But this pattern has a problem — if some thread's `process_range` function throws an exception, the destructor of `threads` will be called during stack unwinding, and as we mentioned earlier, the destructor of a `joinable` thread will call `std::terminate`. To solve this problem, we need to use RAII to wrap the join logic, ensuring correct join even when exceptions occur. We'll implement this improved version in the upcoming "Thread Ownership and RAII" article. +But this pattern has a problem—if a thread's lambda throws an exception, the `std::thread` destructors in the `threads` vector will be called during stack unwinding, and as we said earlier, destroying a `joinable` thread calls `std::terminate`. To solve this, we need to wrap the join logic with RAII to ensure correct joining even if exceptions occur. We will implement this improved version in the upcoming post on "Thread Ownership and RAII." ## Run Online -Experience the three construction methods of std::thread, thread ID queries, and data partitioned parallel processing online: +Experience the three construction methods of `std::thread`, thread ID queries, and data partitioned parallel processing online: ## Summary -In this article, we completed a comprehensive review of the basic interface of `std::thread`. We saw three ways to construct threads — function pointers, lambdas, and functors — whose essence is passing in a callable object and arguments. `join()` and `detach()` are two radically different thread management strategies: join means "wait for me to finish before you go," and detach means "you go first, I'll clean up on my own." If you do nothing and let a `std::thread` destruct, the standard will callously call `std::terminate` — this is C++ using the harshest possible way to remind you: thread lifetimes must be explicitly managed. +In this post, we completed a comprehensive review of the basic interface of `std::thread`. We saw three ways to construct threads—function pointers, lambdas, and functors—whose essence is passing a callable object and arguments. `join()` and `detach()` are two radically different thread management strategies: join is "wait for me to finish," detach is "you go first, I'll clean up." If you do nothing and let a `std::thread` destruct, the standard will mercilessly call `std::terminate`—this is C++ using the strictest way to remind you: thread lifetimes must be explicitly managed. -We also learned about thread identification (`get_id()`), native handles (`native_handle()`), and hardware concurrency queries (`hardware_concurrency()`), as well as a rule that's easy to overlook but crucial: exceptions should not escape thread functions, otherwise `std::terminate` will be triggered. +We also learned about thread identification (`get_id`), native handles (`native_handle`), and hardware concurrency queries (`hardware_concurrency`), as well as a rule that is easily overlooked but crucial: exceptions should not escape thread functions, or `std::terminate` will be triggered. -Finally, we established a basic parallel processing pattern: data partitioning + multithreaded processing + join one by one. This pattern works well in simple scenarios, but it doesn't handle exception safety and RAII — that's the problem we're going to solve next. +Finally, we established a basic parallel processing pattern: data partitioning + multithreading + joining one by one. This pattern works well in simple scenarios, but it lacks exception safety and RAII—which is what we need to solve next. -In the next article, we'll dive into a deeper topic: the thread argument passing mechanism. We'll see how the decay-copy semantics of `std::thread` work, why `std::ref` is a double-edged sword, and what kind of disaster strikes when detach and reference capture are combined. The real pitfalls lie ahead. +In the next post, we will dive into a deeper topic: the thread argument passing mechanism. We will see how the decay-copy semantics of `std::thread` work, why `std::reference_wrapper` is a double-edged sword, and what disasters happen when `detach` combines with reference capture. The real traps are ahead. -> 💡 The complete example code is in [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `code/volumn_codes/vol5/ch01-thread-lifecycle-raii/`. +> 💡 Complete example code is available at [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `vol5/09_std_thread`. ## Exercises ### Exercise 1: Parallel Array Transformation -Given a `std::vector`, use `std::thread` to compute the square root of each element. Requirements: +Given a `std::vector`, use `std::thread` to calculate the square root of each element. Requirements: -1. Use `std::thread::hardware_concurrency()` to get the core count, and decide how many threads to create based on it -2. Each thread processes a range of the array -3. After all threads finish, print the first 10 results for verification +1. Use `hardware_concurrency` to get the core count and decide how many threads to spawn. +2. Each thread processes a segment of the array. +3. After all threads finish, print the first 10 results for verification. -Hint: Watch out for the case where `hardware_concurrency()` might return 0, and handle the situation where the array size isn't evenly divisible by the number of threads. +Hint: Watch out for `hardware_concurrency` possibly returning 0, and how to handle cases where the array size isn't divisible by the thread count. -### Exercise 2: Verify terminate Behavior +### Exercise 2: Verify Terminate Behavior -Write a program that deliberately lets a `std::thread` `joinable` destruct without calling `join()` or `detach()`. Run the program and observe the output when `std::terminate` is called. Then wrap this code with `try-catch` in the `main` function, and see if you can "catch" this terminate — the answer is: no, `std::terminate` cannot be caught by a regular `try-catch`; it's a forced termination of the program. +Write a program that intentionally lets a `joinable` `std::thread`'s destructor run without calling `join()` or `detach()`. Run the program and observe the output when `std::terminate` is called. Then, wrap this code in a `try-catch(...)` block in the `main` function to see if you can "catch" this terminate—the answer is: no, `std::terminate` cannot be caught by a normal `try-catch`, it is a forced termination of the program. ### Exercise 3: Thread ID Mapping -Write a program that creates N threads (for example, four), where each thread stores its `std::this_thread::get_id()` into a shared `std::map` (key is the thread ID, value is the thread number 0-3). Since multiple threads writing to a map simultaneously is a data race, keep it simple for now: have each thread output its result to `std::cout`, and the main thread records it. The purpose of this exercise is to get you familiar with the basic usage of `std::thread::id`. +Write a program that creates N threads (e.g., 4), where each thread stores its `std::thread::id` into a shared `std::map` (key is thread ID, value is thread number 0-3). Since multiple threads writing to a map simultaneously is a data race, let's handle it simply for now: each thread outputs the result to `std::cout`, and the main thread records it. The purpose of this exercise is to familiarize you with the basic usage of `std::thread::id`. ## References @@ -509,5 +305,5 @@ Write a program that creates N threads (for example, four), where each thread st - [std::thread::join — cppreference](https://en.cppreference.com/w/cpp/thread/thread/join) - [std::thread::detach — cppreference](https://en.cppreference.com/w/cpp/thread/thread/detach) - [std::thread::hardware_concurrency — cppreference](https://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency) -- [C++ Core Guidelines: CP.20 — Use RAII, never plain `lock()`/`unlock()`](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp20-use-raii-never-plain-lockunlock) -- [What does `decay_copy` in the constructor of `std::thread` do? — StackOverflow](https://stackoverflow.com/questions/67947814/what-does-decay-copy-in-the-constructor-in-a-stdthread-object-do) +- [C++ Core Guidelines: CP.20 — Use RAII, never plain lock()/unlock()](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp20-use-raii-never-plain-lockunlock) +- [What does decay-copy in the constructor in a std::thread object do? — StackOverflow](https://stackoverflow.com/questions/67947814/what-does-decay-copy-in-the-constructor-in-a-stdthread-object-do) diff --git a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md index 436fc1906..998694783 100644 --- a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md +++ b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md @@ -1,33 +1,33 @@ --- -title: Thread Parameters and Lifetime -description: Dive into thread parameter passing mechanisms, identifying concurrency - bugs caused by dangling references and object destruction order. chapter: 1 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- 内存管理 -difficulty: intermediate -platform: host -reading_time_minutes: 22 cpp_standard: - 11 - 14 - 17 - 20 +description: Dive into thread parameter passing mechanisms, identifying concurrency + bugs caused by dangling references and object destruction order. +difficulty: intermediate +order: 2 +platform: host prerequisites: - std::thread 基础 +reading_time_minutes: 18 related: - 线程所有权与 RAII - CPU cache 与 OS 线程 +tags: +- host +- cpp-modern +- intermediate +- 内存管理 +title: Thread Parameters and Lifetime translation: + engine: anthropic source: documents/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md source_hash: 2acf83ae14bab23867a3ff351e9c1bff052fb15dcbc0ec7428c261e79f3a90e5 - translated_at: '2026-05-20T04:34:03.498432+00:00' - engine: anthropic token_count: 3850 + translated_at: '2026-05-20T04:34:03.498432+00:00' --- # Thread Parameters and Lifetimes diff --git a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md index 9116e83f4..7c01fdc80 100644 --- a/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md +++ b/documents/en/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md @@ -1,33 +1,33 @@ --- -title: Thread Ownership and RAII -description: Wrapping `std::thread` with RAII to implement an exception-safe `joining_thread` - guard and scope-exit cleanup chapter: 1 -order: 3 -tags: -- host -- cpp-modern -- intermediate -- RAII -difficulty: intermediate -platform: host -reading_time_minutes: 20 cpp_standard: - 11 - 14 - 17 - 20 +description: Wrapping `std::thread` with RAII to implement an exception-safe `joining_thread` + guard and scope-exit cleanup +difficulty: intermediate +order: 3 +platform: host prerequisites: - 线程参数与生命周期 +reading_time_minutes: 18 related: - mutex 与 RAII 锁 - jthread 与停止令牌 +tags: +- host +- cpp-modern +- intermediate +- RAII +title: Thread Ownership and RAII translation: + engine: anthropic source: documents/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md source_hash: f8c117653d9fde2694952716e1b91c176973c6f3621e15a88b4cb1a613fdbc81 - translated_at: '2026-05-20T04:35:21.082754+00:00' - engine: anthropic token_count: 3758 + translated_at: '2026-05-20T04:35:21.082754+00:00' --- # Thread Ownership and RAII diff --git a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md index 144446900..8b31e90ac 100644 --- a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md +++ b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md @@ -1,413 +1,295 @@ --- -title: Mutex and RAII Locks -description: A systematic overview of the mutex family and RAII (Resource Acquisition - Is Initialization) lock guards, covering the evolution and best practices from `lock_guard` - to `scoped_lock`. chapter: 2 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- mutex -- RAII守卫 -difficulty: intermediate -platform: host -reading_time_minutes: 22 cpp_standard: - 11 - 14 - 17 - 20 +description: Systematically review the mutex family and RAII lock guards, covering + the evolution from `lock_guard` to `scoped_lock` and best practices. +difficulty: intermediate +order: 1 +platform: host prerequisites: - 线程所有权与 RAII +reading_time_minutes: 15 related: - 死锁与锁顺序 - condition_variable 与等待语义 +tags: +- host +- cpp-modern +- intermediate +- mutex +- RAII守卫 +title: Mutex and RAII Lock translation: - source: documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md - source_hash: 41343836ecc79404aeb259d2ddd0e258de218c2c01671751292ee5ae1cbd6d3e - translated_at: '2026-05-26T11:42:08.230599+00:00' engine: anthropic - token_count: 3182 + source: documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md + source_hash: 04b39e9664388f01aa57d869f1713f276df3a6ba7565c60dce941f9d16181c72 + token_count: 3181 + translated_at: '2026-06-15T09:24:57.429078+00:00' --- # mutex and RAII Locks -In the previous article, we discussed thread ownership and RAII, mastering the lifetime management of `std::thread` and the scope-based resource control approach. Now a question arises: with threads in place, how do we safely share data between them? We have already seen the destructive power of data races in the article on fundamental concurrency issues—two threads writing to the same `int` simultaneously can cause a result of 2,000,000 to drop to 1,345,687. The most common solution to data races is the mutex, and the C++ standard library provides an entire mutex family along with matching RAII lock guards. +In the previous post, we discussed thread ownership and RAII, mastering the lifecycle management of `std::thread` and the concept of scope-based resource control. Now, the question arises: with threads in play, how do they safely share data? We have already seen the power of data races in the concurrency basics post—two threads writing to the same `int` can result in 1,345,687 instead of 2,000,000. The most common solution to data races is the mutex, and the C++ Standard Library provides a whole family of mutexes and accompanying RAII lock guards. -Our goal in this article is clear: first, we will walk through the four members of the mutex family—`std::mutex`, `std::recursive_mutex`, `std::timed_mutex`, and `std::recursive_timed_mutex`—to understand what problem each one solves. Then, we will systematically review the three RAII lock guards—`std::lock_guard`, `std::unique_lock`, and `std::scoped_lock`—which are the tools that should actually appear in our daily code. Throughout this process, we will repeatedly emphasize one principle: absolutely never manually call `lock()` and `unlock()`. +In this post, our goal is clear: first, we will go through the four members of the mutex family—`std::mutex`, `std::recursive_mutex`, `std::timed_mutex`, and `std::shared_mutex`—one by one to understand what problems they solve. Then, we will systematically review three RAII lock guards—`std::lock_guard`, `std::unique_lock`, and `std::scoped_lock`—which are the tools that should actually appear in our daily code. Throughout this process, we will repeatedly emphasize one principle: never manually call `lock()` and `unlock()`. -## std::mutex: The Most Basic Mutex +## std::mutex: The Basic Mutex -`std::mutex` is the standard mutex introduced in C++11, defined in the `` header. It provides only three operations: `lock()`, `unlock()`, and `try_lock()`. +`std::mutex` is the standard mutex introduced in C++11, defined in the `` header file. It provides only three operations: `lock()`, `unlock()`, and `try_lock()`. -`lock()` is a blocking call—if the mutex is already held by another thread, the current thread blocks and waits until it acquires the lock. `unlock()` releases the lock. `try_lock()` is the non-blocking version—it attempts to acquire the lock, returning `true` on success and `false` on failure, without waiting. These three operations constitute the entire interface of the mutex, simple to an astonishing degree. +`lock()` is a blocking call—if the mutex is already held by another thread, the current thread blocks and waits until it acquires the lock. `unlock()` releases the lock. `try_lock()` is the non-blocking version—it attempts to acquire the lock, returning `true` on success and `false` on failure, without waiting. These three operations constitute the entire interface of a mutex, simple enough to be suspicious. -Don't be too quick to assume that simplicity means there are no pitfalls. Take a look at this "handcrafted" style of code: +Don't rush to think simplicity means no pitfalls. Look at this "hand-crafted" code: ```cpp -#include -#include - std::mutex mtx; -int shared_counter = 0; +int counter = 0; -void bad_increment() -{ - mtx.lock(); // 手动加锁 - shared_counter++; - // 如果这里抛出异常... unlock 永远不会执行 - mtx.unlock(); // 手动解锁 +void unsafe_increment() { + mtx.lock(); + // ... do some work ... + counter++; + // ... more work that might throw ... + mtx.unlock(); } ``` -This code works on the normal path, but it has several fatal hidden dangers. If any exception is thrown between `m.lock()` and `m.unlock()` (of course, incrementing an `int` won't throw, but what if you replace it with a complex type, or intersperse other operations that might throw?), `m.unlock()` will never be executed. With the lock not released, all other threads waiting for this lock will block—this isn't a dead lock, but the effect is similar, and it's even harder to track down because the program doesn't freeze in an obvious loop-wait; it just "inexplicably" stops. +This code works under normal paths, but it has several fatal hidden dangers. If an exception is thrown between `mtx.lock()` and `mtx.unlock()` (of course, `counter++` won't throw, but what if you replace `counter` with a complex type, or insert other operations that might throw in between?), `mtx.unlock()` will never be executed. The lock isn't released, and all other threads waiting for this lock block—this isn't strictly a deadlock, but the effect is similar, and it's harder to debug because the program doesn't freeze in an obvious loop, but rather "mysteriously" stops. -An even worse scenario involves multiple return paths. If there are three or four `if` branches in the middle of your critical section, you have to write `m.unlock()` before each branch. Missing even one is a bug. In a large codebase, this "manual lock/unlock pairing" pattern is virtually impossible to guarantee for correctness. +A worse scenario involves multiple return paths. If you have three or four `if` branches inside your critical section, you need to write `mtx.unlock()` before each branch. Missing one means a bug. In large codebases, this "manual pairing of lock/unlock" pattern is nearly impossible to guarantee correctness. -There is also a classic pitfall: the same lock being acquired twice by the same thread. `std::mutex` does not allow a thread to repeatedly lock it—if you call `lock()` while already holding the lock, the result is undefined behavior (most implementations will dead lock immediately). This is easy to stumble into unknowingly when the function call chain is complex: +There is also a classic pitfall: the same lock being locked twice by the same thread. `std::mutex` does not allow the same thread to lock repeatedly—if you call `lock()` while already holding the lock, the result is undefined behavior (most implementations will deadlock immediately). This is easy to stumble into unknowingly when function call chains are complex: ```cpp -std::mutex mtx; - -void function_a() -{ +void bad_recursive_call() { mtx.lock(); - function_b(); // function_b 内部也锁了同一把 mutex - mtx.unlock(); -} - -void function_b() -{ - mtx.lock(); // 死锁!同一线程对 std::mutex 重复加锁 - // ... + // ... some logic ... + bad_recursive_call(); // Oops, deadlock here! mtx.unlock(); } ``` -So the conclusion is clear: the direct interface of `std::mutex` should not appear in application code. Its design intent is to serve as the underlying cornerstone for RAII wrappers, not for you to manually call `lock()`/`unlock()` every day. +So the conclusion is clear: the direct interface of `std::mutex` should not appear in application code. Its design intent is to serve as the underlying cornerstone for RAII wrappers, not for you to manually `lock()`/`unlock()` every day. -## std::recursive_mutex: Allowing Repeated Locking by the Same Thread +## std::recursive_mutex: Allowing Recursive Locking -`std::recursive_mutex` solves the "repeated locking by the same thread" problem mentioned above. It internally maintains a lock counter—the first `lock()` by the same thread sets the counter to 1, the second to 2, and so on; each `unlock()` decrements the counter by 1, and the lock is only truly released when the counter reaches 0. +`std::recursive_mutex` solves the "same thread re-locking" problem mentioned above. It internally maintains a lock counter—the first time a thread locks it, the counter becomes 1; the second time, 2; and so on. Each call to `unlock()` decrements the counter; the lock is only truly released when the counter reaches 0. ```cpp -#include -#include - -std::recursive_mutex rmtx; +std::recursive_mutex rec_mtx; -void recursive_function(int depth) -{ - std::lock_guard lock(rmtx); - std::cout << "depth = " << depth << "\n"; - if (depth > 0) { - recursive_function(depth - 1); // 递归调用,再次加锁 +void recursive_function(int n) { + std::lock_guard lock(rec_mtx); + if (n > 0) { + recursive_function(n - 1); } } - -int main() -{ - recursive_function(5); - return 0; -} ``` -This code is perfectly legal—`std::recursive_mutex` allows the same thread to lock multiple times, each recursive call increments the counter, and each return triggers the destructor of `std::lock_guard` to decrement the counter. The lock is only truly released when the outermost function returns. +This code is completely legal—`std::recursive_mutex` allows the same thread to lock multiple times. Each recursive call increases the counter, and each return triggers the destructor of the `std::unique_lock` (or `lock_guard`) to decrement the counter. The lock is only truly released when the outermost function returns. -However, `std::recursive_mutex` is usually a signal of a design smell. If you need a recursive lock, it is highly likely because your interface design mixes "functions that need to be called under lock protection" with "internal implementations that don't need the lock." A better approach is to extract the "operations under lock protection" into an unlocked internal function, and let the outer interface handle the locking. A recursive lock is a crutch—it can help you walk, but you shouldn't rely on it. +However, `std::recursive_mutex` is often a signal of a design smell. If you need a recursive lock, it's likely because your interface design mixes "functions that need to be called under lock protection" with "internal implementations that don't need locks." A better approach is to extract the "operations under lock protection" into an internal function without locking, and let the outer interface handle the locking. A recursive lock is a crutch; it helps you walk, but you shouldn't rely on it. ## std::timed_mutex: Mutex with Timeout -`std::timed_mutex` adds two timeout-based locking operations on top of `std::mutex`: `try_lock_for()` and `try_lock_until()`. +`std::timed_mutex` adds two timeout-based locking operations to `std::mutex`: `try_lock_for()` and `try_lock_until()`. -`try_lock_for()` accepts a time duration (`std::chrono::duration`), repeatedly attempting to acquire the lock within the specified time, and returns `false` on timeout. `try_lock_until()` accepts an absolute time point (`std::chrono::time_point`), attempting to acquire the lock before the specified moment, and returns `false` on timeout. The difference between the two is similar to "wait for at most 100 milliseconds" versus "wait until 3 PM." +`try_lock_for()` accepts a time duration (`std::chrono::duration`), repeatedly attempting to acquire the lock within the specified time, and returns `false` on timeout. `try_lock_until()` accepts an absolute time point (`std::chrono::time_point`), attempting to acquire the lock before the specified moment, and returns `false` on timeout. The difference is similar to "wait for at most 100 milliseconds" versus "wait until 3 PM." ```cpp -#include -#include -#include - -std::timed_mutex tmtx; +std::timed_mutex t_mtx; -void try_with_timeout() -{ - if (tmtx.try_lock_for(std::chrono::milliseconds(100))) { - // 成功获取锁 - std::cout << "Lock acquired within 100ms\n"; - // ... 临界区操作 ... - tmtx.unlock(); +void try_update() { + if (t_mtx.try_lock_for(std::chrono::milliseconds(100))) { + std::lock_guard lock(t_mtx, std::adopt_lock); + // Critical section } else { - // 超时,锁获取失败 - std::cout << "Failed to acquire lock within 100ms\n"; - // 可以做降级处理、记录日志、或者稍后重试 + // Handle timeout } } ``` -`std::recursive_timed_mutex` is a combination of a recursive lock and a timed lock—the same thread can lock multiple times, while also supporting `try_lock_for()` and `try_lock_until()`. It is rarely used in practical engineering; just knowing it exists is enough. +`std::recursive_timed_mutex` is a combination of a recursive lock and a timed lock—the same thread can lock multiple times, and it supports `try_lock_for()` and `try_lock_until()`. It is rarely used in actual engineering; just knowing it exists is enough. -A quick note here: timed locks have higher overhead on some platforms because they need to interact with the system clock. If your scenario doesn't require timeout capabilities, a plain `std::mutex` is sufficient. Don't default to `std::timed_mutex` just because "it might come in handy." +A quick reminder: locks with timeouts can have higher overhead on some platforms because they interact with the system clock. If your scenario doesn't require timeout capability, a regular `std::mutex` is sufficient. Don't default to `std::timed_mutex` just "in case." ## std::lock_guard: The Simplest RAII Wrapper -We have finally arrived at the tools we should actually use. `std::lock_guard` is the lightest RAII lock guard introduced in C++11—it calls `lock()` on construction and `unlock()` on destruction, that's it. It doesn't accept `std::defer_lock`, has no `unlock()` method, and doesn't support moving—it has no extra capabilities whatsoever, but it is precisely this minimalist design that guarantees you can't use it incorrectly. +Finally, we arrive at the tools we should actually use. `std::lock_guard` is the lightest weight RAII lock guard introduced in C++11—it calls `lock()` on construction and `unlock()` on destruction. That's it. It doesn't accept `defer_lock`, has no `unlock()` method, and doesn't support movement—it has no extra capabilities, but it is precisely this minimalist design that guarantees you can't use it incorrectly. ```cpp -#include -#include -#include - std::mutex mtx; -std::vector shared_data; - -void safe_push(int value) -{ - std::lock_guard lock(mtx); // 构造时自动 lock - shared_data.push_back(value); - // 无论正常返回、异常抛出、还是 early return,析构时都会 unlock -} +void critical_task() { + std::lock_guard lock(mtx); + // Critical section +} // Lock released automatically ``` -Note a common mistake made by beginners—forgetting to name the `std::lock_guard` variable: +Note a common mistake beginners make—forgetting to name the `std::lock_guard` variable: ```cpp -void bad_push(int value) -{ - std::lock_guard(mtx); // 临时对象!立刻析构! - shared_data.push_back(value); // 没有锁保护 -} - -void good_push(int value) -{ - std::lock_guard lock(mtx); // lock 有名字,生命周期是整个作用域 - shared_data.push_back(value); -} +// WRONG: Temporary object destroyed immediately! +std::lock_guard(mtx); ``` -An unnamed temporary object is destructed immediately at the end of the statement—the lock is released the moment it is acquired, which is equivalent to not locking at all. Compilers usually don't issue warnings for this situation, so always remember to give the lock object a name. +An unnamed temporary object is destructed immediately when the statement ends—the lock is released just as soon as it's acquired, which is equivalent to not locking at all. Compilers usually don't warn about this, so remember to name your lock objects. -`std::lock_guard` has a less commonly used but worth-knowing constructor option: `std::adopt_lock`. It tells `std::lock_guard`: "The lock is already held by the current thread; just handle releasing it on destruction, don't lock again." This option is primarily used in conjunction with the `std::lock()` function—first acquire multiple locks simultaneously via `std::lock()`, then hand them over to `std::lock_guard` using `std::adopt_lock`. We will see the specific usage when we discuss dead lock prevention in the next article. +`std::lock_guard` has a rarely used but worth-knowing constructor option: `std::adopt_lock`. It tells `std::lock_guard`: "The lock is already held by the current thread, just manage the release on destruction, don't lock again." This option is mainly used to cooperate with the `std::lock()` function—first acquire multiple locks simultaneously via `std::lock()`, then hand them over to `std::lock_guard` for management using `std::adopt_lock`. We will see specific usage in the next post when discussing deadlock prevention. ## std::unique_lock: The Flexible but Not Heavy Swiss Army Knife -If `std::lock_guard` is a reliable screwdriver, `std::unique_lock` is a Swiss army knife. On top of `std::lock_guard`, it adds several key capabilities: deferred locking, manual unlocking, lock ownership transfer, and cooperation with condition variables. Of course, the extra capabilities also mean extra state—`std::unique_lock` internally needs to store an "whether the lock is held" flag, making its overhead slightly larger than `std::lock_guard`, but in the vast majority of scenarios, this difference is negligible. +If `std::lock_guard` is a reliable screwdriver, `std::unique_lock` is a Swiss Army knife. Based on `std::lock_guard`, it adds several key capabilities: deferred locking, manual unlocking, lock ownership transfer, and cooperation with condition variables. Of course, extra capabilities mean extra state—`std::unique_lock` needs to store an "owns lock" flag internally, so the overhead is slightly higher than `std::lock_guard`, but in the vast majority of scenarios, this difference is negligible. ### Basic Usage: As Simple as lock_guard ```cpp -#include - std::mutex mtx; - -void basic_unique_lock() -{ - std::unique_lock lock(mtx); // 构造时加锁,析构时解锁 - // 临界区... +void task() { + std::unique_lock lock(mtx); + // Critical section } ``` -The most basic usage is exactly the same as `std::lock_guard`: lock on construction, unlock on destruction. +The most basic usage is exactly the same as `std::lock_guard`: construct to lock, destruct to unlock. ### Deferred Locking: defer_lock -`std::defer_lock` tells `std::unique_lock` not to lock upon construction; we decide when to lock later. This is useful in "conditional locking" scenarios—not all code paths need the lock, but you want to enjoy RAII protection on the paths that do: +`std::defer_lock` tells `std::unique_lock` not to lock upon construction; we decide when to lock later. This is useful in "conditional locking" scenarios—not all code paths need a lock, but you want to enjoy RAII protection on the paths that do: ```cpp -#include - -std::mutex mtx; -bool needs_sync = true; // 假设由外部条件决定 - -void conditional_lock() -{ - std::unique_lock lock(mtx, std::defer_lock); // 构造时不加锁 - - if (needs_sync) { - lock.lock(); // 按需加锁 - } - - // ... 无论加没加锁,析构时都能正确处理 +std::unique_lock lock(mtx, std::defer_lock); +// ... do some unlocked work ... +if (need_lock) { + lock.lock(); + // Critical section } ``` -A more common use of `std::defer_lock` is in conjunction with `std::lock()` to achieve safe acquisition of multiple locks—first construct two `std::unique_lock`s with `std::defer_lock`, then use `std::lock()` to lock them simultaneously. This pattern will be detailed in the next article. +`std::defer_lock` is more commonly used to cooperate with `std::lock` to implement safe multi-lock acquisition—first construct two `std::unique_lock`s with `std::defer_lock`, then use `std::lock` to lock them simultaneously. This pattern will be expanded in the next post. -### Early Unlocking: Shrinking the Critical Section +### Early Unlock: Reducing the Critical Section `std::unique_lock` allows you to manually call `unlock()` before the scope ends—this is valuable when you need to shrink the critical section. The shorter the lock is held, the shorter the wait time for other threads, and the higher the concurrency: ```cpp -#include -#include -#include - +std::vector data; std::mutex mtx; -std::vector shared_data; - -void process_and_save() -{ - std::unique_lock lock(mtx); - - // 在锁的保护下拷贝数据 - auto snapshot = shared_data; - - lock.unlock(); // 临界区结束,提前解锁 - - // 在锁外做耗时操作——不会阻塞其他线程 - for (auto& v : snapshot) { - v *= 2; - } - // 保存到文件也是锁外的操作 - std::ofstream ofs("output.txt"); - for (int v : snapshot) { - ofs << v << "\n"; +void process_data() { + std::vector local_copy; + { + std::unique_lock lock(mtx); + local_copy = data; // Copy under lock + lock.unlock(); // Release early } + // Process local_copy without holding the lock + // ... heavy computation ... } ``` -This example demonstrates an important pattern: quickly complete the necessary data copy under the protection of the lock, then release the lock immediately, and perform subsequent processing outside the lock. `std::lock_guard` cannot unlock early—its design philosophy is "the lifetime of the lock equals the lifetime of the scope," with no exceptions. +This example demonstrates an important pattern: quickly complete necessary data copying under the protection of the lock, then immediately release the lock, and perform subsequent processing outside the lock. `std::lock_guard` cannot unlock early—its design philosophy is "lock lifecycle equals scope lifecycle," with no exceptions. -### Cooperation with Condition Variables +### Cooperating with Condition Variables -This is the most irreplaceable scenario for `std::unique_lock`. The `wait()` family of functions in `std::condition_variable` requires a `std::unique_lock` to be passed in; you cannot use `std::lock_guard`. The reason lies in the working mechanism of condition variables: a thread must release the lock while waiting (so other threads can enter the critical section to modify the condition), and it must re-acquire the lock when woken up. The "unlock-then-relock" capability provided by `std::unique_lock` is exactly what condition variables need. +This is the most irreplaceable scenario for `std::unique_lock`. The `wait()` series of functions of `std::condition_variable` require passing in `std::unique_lock`, not `std::lock_guard`. The reason lies in the working mechanism of condition variables: a thread must release the lock when waiting (to allow other threads to enter the critical section and modify the condition), and re-acquire the lock when woken up. The "unlock-then-relock" capability provided by `std::unique_lock` is exactly what condition variables need. ```cpp -#include -#include -#include -#include - -template -class ThreadSafeQueue { -public: - void push(const T& value) - { - { - std::lock_guard lock(mtx_); - queue_.push(value); - } - cv_.notify_one(); - } - - T pop() - { - std::unique_lock lock(mtx_); // 必须用 unique_lock - cv_.wait(lock, [this] { return !queue_.empty(); }); - // wait 内部:条件不满足 -> unlock -> 等待 -> 被唤醒 -> re-lock -> 检查条件 - - T value = std::move(queue_.front()); - queue_.pop(); - return value; - } +std::mutex mtx; +std::condition_variable cv; +bool ready = false; -private: - std::queue queue_; - mutable std::mutex mtx_; - std::condition_variable cv_; -}; +void wait_for_ready() { + std::unique_lock lock(mtx); + cv.wait(lock, [] { return ready; }); + // ... +} ``` -If you try to replace the `std::unique_lock` in the code with `std::lock_guard`, it won't even compile—the signature of `wait()` requires a `std::unique_lock`. +If you try to swap the `std::unique_lock` inside `cv.wait` with `std::lock_guard`, it won't even compile—the signature of `wait` requires `std::unique_lock`. ### Lock Ownership Transfer -`std::unique_lock` supports move semantics, allowing lock ownership to be transferred between functions. This is useful in certain architectural designs—for example, a function acquires the lock and does some initialization work, then transfers the lock ownership to the caller, who is responsible for subsequent critical section operations and the final unlocking: +`std::unique_lock` supports move semantics, allowing lock ownership to be transferred between functions. This is useful in some architectural designs—for example, a function acquires a lock and does some initialization work, then transfers the lock ownership to the caller, who is responsible for subsequent critical section operations and final unlocking: ```cpp -#include - -std::mutex mtx; - -std::unique_lock acquire_and_initialize() -{ +std::unique_lock acquire_and_process() { std::unique_lock lock(mtx); - // 做一些需要锁保护的初始化工作 - prepare_shared_state(); - return lock; // NRVO 或移动返回,锁的所有权转移给调用者 + // Init logic + return lock; // Move ownership } -void use_lock() -{ - std::unique_lock lock = acquire_and_initialize(); - // lock 持有锁,可以在临界区操作 - modify_shared_state(); - // lock 离开作用域时自动解锁 +void consumer() { + auto lock = acquire_and_process(); + // Continue critical section } ``` -Note that `std::lock_guard` does not support moving—both its copy constructor and move constructor are deleted. If you need to transfer lock ownership, `std::unique_lock` is the only choice. +Note that `std::lock_guard` does not support movement—both its copy constructor and move constructor are deleted. If you need to transfer lock ownership, `std::unique_lock` is the only choice. -## std::scoped_lock: C++17's Multi-Lock Deadlock Prevention +## std::scoped_lock: C++17 Multi-Lock Deadlock Prevention -`std::scoped_lock` is an RAII lock guard introduced in C++17, specifically designed for multi-lock scenarios. Its constructor can accept any number of mutexes (it also accepts a single mutex, of course), and internally uses the dead lock avoidance algorithm provided by `std::lock()` to acquire all locks at once, releasing them in reverse order upon destruction. +`std::scoped_lock` is an RAII lock guard introduced in C++17, designed specifically for multi-lock scenarios. Its constructor can accept any number of mutexes (it also accepts a single mutex), and it uses the deadlock avoidance algorithm provided by `std::lock` to acquire all locks at once, releasing them in reverse order upon destruction. -This feature solves a very practical problem. Suppose two threads need to simultaneously operate on two data structures protected by different mutexes. The most naive approach is to nest `std::lock_guard`s: +This feature solves a very real problem. Suppose two threads need to operate on two data structures protected by different mutexes simultaneously. The most naive approach is to nest `std::lock_guard`: ```cpp -#include -#include - -std::mutex mtx_a; -std::mutex mtx_b; - -void thread1() +// Thread 1 { - std::lock_guard lock_a(mtx_a); // 先锁 A - std::cout << "thread1: locked A\n"; - std::lock_guard lock_b(mtx_b); // 再锁 B - std::cout << "thread1: locked both\n"; + std::lock_guard lock1(mtx1); + std::lock_guard lock2(mtx2); + // ... } -void thread2() +// Thread 2 { - std::lock_guard lock_b(mtx_b); // 先锁 B - std::cout << "thread2: locked B\n"; - std::lock_guard lock_a(mtx_a); // 再锁 A - std::cout << "thread2: locked both\n"; + std::lock_guard lock2(mtx2); + std::lock_guard lock1(mtx1); + // ... } ``` -If thread1 acquires `mutex_a` while thread2 acquires `mutex_b`, both sides get stuck—the classic AB-BA dead lock. `std::scoped_lock` solves this with a single line of code: +If Thread 1 grabs `mtx1` while Thread 2 grabs `mtx2`, both sides get stuck—the classic AB-BA deadlock. `std::scoped_lock` solves this in one line: ```cpp -void safe_thread() +// Both threads { - std::scoped_lock lock(mtx_a, mtx_b); // 一次性安全获取两把锁 - // 临界区... + std::scoped_lock lock(mtx1, mtx2); + // ... } ``` -The dead lock avoidance algorithm inside `std::scoped_lock` is based on a `std::lock()` backoff strategy: it tries to acquire all locks in a certain order, and if a certain `try_lock()` fails, it releases the already acquired locks and retries in a different order. This algorithm breaks the "hold and wait" condition among the four necessary conditions for dead lock—if acquisition fails, already held locks are released, eliminating the situation of "holding one lock while waiting for another." +The internal deadlock avoidance algorithm of `std::scoped_lock` is based on a `std::lock` backoff strategy: try to acquire all locks in a certain order; if a specific lock fails, release the acquired locks and retry in a different order. This algorithm breaks the "hold and wait" condition of the four necessary conditions for deadlock—if acquisition fails, held locks are released, eliminating the situation of "holding one while waiting for another." -`std::scoped_lock` can also be used for a single mutex scenario, where it is equivalent to `std::lock_guard`. But for the clarity of code intent, we still recommend using `std::lock_guard` for single-lock scenarios—seeing `std::lock_guard` tells you there is only one lock, and seeing `std::scoped_lock` tells you multiple locks might be involved. This is valuable information for anyone reading the code. +`std::scoped_lock` can also be used for a single mutex, in which case it is equivalent to `std::lock_guard`. However, for code clarity, it is still recommended to use `std::lock_guard` for single-lock scenarios—seeing `std::lock_guard` tells you there is only one lock, seeing `std::scoped_lock` implies multiple locks might be involved, which is valuable information for anyone reading the code. ## lock_guard vs unique_lock vs scoped_lock: Selection Guide -Let's put the core differences of the three RAII lock guards together to help you make quick choices in actual development. +Let's compare the core differences of the three RAII lock guards to help you make quick choices in actual development. -The design philosophy of `std::lock_guard` is "simplicity is beauty." It is neither copyable nor movable, cannot unlock early, and cannot defer locking—these "limitations" are precisely its advantages, because the more restrictions there are, the less room there is for error. For 90% of daily scenarios, `std::lock_guard` is enough: enter the function, construct the `std::lock_guard`, operate on shared data, return from the function, and the `std::lock_guard` destructs to release the lock. The entire process is a straight line with no branches. +The design philosophy of `std::lock_guard` is "simplicity is beauty." It is non-copyable, non-movable, cannot unlock early, and cannot defer locking—these "limitations" are precisely its strengths, because the more restrictions, the smaller the room for error. For 90% of daily scenarios, `std::lock_guard` is sufficient: enter function, construct `std::lock_guard`, operate on shared data, function returns, `std::lock_guard` destructs and releases the lock. The whole process is a straight line with no branches. -`std::unique_lock` is suited for the 10% of scenarios that require extra flexibility. The most typical is cooperation with condition variables—this is the irreplaceable core scenario for `std::unique_lock`. Next is the "copy data first, then unlock early" pattern—moving time-consuming operations outside the lock to reduce lock hold time. There are also deferred locking and lock ownership transfer, which are used in more complex architectural designs. +`std::unique_lock` fits that 10% of scenarios requiring extra flexibility. The most typical is cooperating with condition variables—this is the core scenario where `std::lock_guard` is irreplaceable. Next is the "copy data first, then unlock early" pattern—moving time-consuming operations outside the lock to reduce hold time. There are also deferred locking and lock ownership transfer, which are used in more complex architectural designs. -The core value of `std::scoped_lock` is dead lock prevention for multi-lock acquisition. As long as your code needs to hold two or more locks simultaneously, you should use `std::scoped_lock`. If the project has already adopted C++17, using `std::scoped_lock` for single-lock scenarios is also perfectly fine—but in terms of team conventions, distinguishing `std::lock_guard` (single lock) and `std::scoped_lock` (multiple locks) helps with code readability and maintainability. +The core value of `std::scoped_lock` is deadlock prevention for multi-lock acquisition. Whenever your code needs to hold two or more locks simultaneously, you should use `std::scoped_lock`. If the project has already adopted C++17, using `std::scoped_lock` for single-lock scenarios is also perfectly fine—but in terms of team convention, distinguishing `std::lock_guard` (single lock) and `std::scoped_lock` (multi-lock) helps code readability and maintainability. -## Engineering Principle: Absolutely Never Manually Call lock()/unlock() +## Engineering Principle: Never Manually Call lock()/unlock() -We spent an entire article discussing the mutex family and RAII lock guards, and the core principle we want to emphasize in the end is just one: absolutely never directly call `lock()` and `unlock()` in application code. We have repeatedly seen the reasons earlier—manually managing lock/unlock is virtually impossible to guarantee for correctness in scenarios with exception paths, multiple return paths, and nested calls, whereas RAII lock guards fundamentally eliminate this entire class of bugs by binding the lock's lifetime to the scope. +We spent an entire post discussing the mutex family and RAII lock guards, and the core principle to emphasize is only one: never directly call `lock()` and `unlock()` in application code. We have seen the reasons repeatedly throughout the text—managing lock/unlock manually is almost impossible to guarantee correctness in scenarios involving exception paths, multiple return paths, and nested calls, whereas RAII lock guards fundamentally eliminate this entire class of bugs by binding the lock lifecycle to the scope. -This principle is explicitly recorded in the C++ Core Guidelines as CP.20: "Use RAII, never plain `lock()`/`unlock()`." The only exception is `std::adopt_lock`—it accepts a mutex that is already locked, and is only responsible for unlocking on destruction. But even in this case, the locking action should be done through `std::lock()` or other safe mechanisms, not by manually calling `lock()`. +This principle is explicitly recorded in the C++ Core Guidelines as CP.20: "Use RAII, never plain `lock()`/`unlock()`." The only exception is `std::adopt_lock`—it accepts an already locked mutex and is only responsible for unlocking upon destruction. But even in this case, the locking action should be done through `std::lock()` or other safe mechanisms, not by manually calling `lock()`. -> 💡 The complete example code is in [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `vol34567/10_mutex_raii.cpp`. +> 💡 Complete example code is available at [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `code/examples/vol5/10_mutex_raii.cpp`. ## Run Online -Experience the three RAII lock guards: lock_guard, unique_lock + condition_variable, and scoped_lock online: +Experience the three RAII lock guards: `lock_guard`, `unique_lock` + `condition_variable`, and `scoped_lock` online: @@ -416,17 +298,17 @@ Experience the three RAII lock guards: lock_guard, unique_lock + condition_varia ### Exercise 1: Implement a Thread-Safe Wrapper for stack -Given a `std::stack`, use `std::mutex` and `std::lock_guard` to implement a thread-safe wrapper for it. You are required to provide four interfaces: `push()`, `pop()` (returns `std::optional`, returning `std::nullopt` when the stack is empty), `top()` (also returns `std::optional`), and `empty()`. Hint: note that `pop()` and `top()` cannot return references—because after unlocking, if the caller accesses the reference, it becomes invalid. +Given a `std::stack`, use `std::mutex` and `std::lock_guard` to implement a thread-safe wrapper for it. Requirements: provide `push`, `pop` (returns `T`, returns `std::optional` if empty), `try_pop` (also returns `std::optional`), and `size` interfaces. Hint: Note that `pop` and `try_pop` should not return references—because after unlocking, if the caller accesses the reference, it becomes invalid. -### Exercise 2: Compare the Performance of lock_guard and unique_lock +### Exercise 2: Compare Performance of lock_guard and unique_lock -Write a simple benchmark: use four threads to each increment a shared counter 1,000,000 times, protected by `std::lock_guard` and `std::unique_lock` respectively. Compare the running times of the two—you will find that the difference is usually within the noise range, but in extreme scenarios, the extra state maintenance of `std::unique_lock` might manifest as measurable overhead. Question: under what conditions would this difference become significant? +Write a simple benchmark: use 4 threads to increment a shared counter 1,000,000 times each, protected by `std::lock_guard` and `std::unique_lock` respectively. Compare their runtimes—you will find the difference is usually within the noise range, but in extreme scenarios, the extra state maintenance of `std::unique_lock` might manifest as measurable overhead. Think: Under what conditions does this difference become significant? -### Exercise 3: Safely Swap Two Protected Data Structures with scoped_lock +### Exercise 3: Safely Swap Two Protected Data with scoped_lock -Suppose there are two `std::vector`s, each protected by a `std::mutex`. Write a `safe_swap()` function that uses `std::scoped_lock` to acquire both locks simultaneously, then swaps the contents of the two vectors. Verify that repeatedly calling this function in a multi-threaded environment does not dead lock. +Assume there are two `std::vector`, each protected by a `std::mutex`. Write a `swap_data` function that uses `std::scoped_lock` to acquire both locks simultaneously, then swaps the contents of the two vectors. Verify that calling this function repeatedly in a multi-threaded environment does not cause a deadlock. -## Reference Resources +## References - [std::mutex -- cppreference](https://en.cppreference.com/w/cpp/thread/mutex) - [std::recursive_mutex -- cppreference](https://en.cppreference.com/w/cpp/thread/recursive_mutex) diff --git a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md index b0c56c8f1..eee4a9fc1 100644 --- a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md +++ b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md @@ -1,33 +1,33 @@ --- -title: Deadlock and Lock Ordering -description: Dive into the four necessary conditions for dead lock, and master lock - ordering constraints, `try_lock` fallbacks, and `scoped_lock` multi-lock acquisition - strategies. chapter: 2 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- mutex -difficulty: intermediate -platform: host -reading_time_minutes: 20 cpp_standard: - 11 - 14 - 17 - 20 +description: Dive into the four necessary conditions for dead lock, and master lock + ordering constraints, `try_lock` fallbacks, and `scoped_lock` multi-lock acquisition + strategies. +difficulty: intermediate +order: 2 +platform: host prerequisites: - mutex 与 RAII 锁 +reading_time_minutes: 18 related: - condition_variable 与等待语义 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: Deadlock and Lock Ordering translation: + engine: anthropic source: documents/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md source_hash: 3610a18246675fc93df655733191a20ffacfe350c0b8125cab6c1c970e7d6a96 - translated_at: '2026-05-20T04:36:27.588635+00:00' - engine: anthropic token_count: 3600 + translated_at: '2026-05-20T04:36:27.588635+00:00' --- # Deadlock and Lock Ordering diff --git a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md index 5a813e940..4fbb37bc3 100644 --- a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md +++ b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md @@ -1,34 +1,34 @@ --- -title: condition_variable and Wait Semantics -description: Master the wait/notify mechanism of condition variables, and understand - spurious wakeups, predicate patterns, and lost wakeups. chapter: 2 -order: 3 -tags: -- host -- cpp-modern -- intermediate -- mutex -- 异步编程 -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Master the wait/notify mechanism of condition variables, and understand + spurious wakeups, predicate patterns, and lost wakeups. +difficulty: intermediate +order: 3 +platform: host prerequisites: - mutex 与 RAII 锁 +reading_time_minutes: 18 related: - 读写锁与 shared_mutex - 线程安全队列 +tags: +- host +- cpp-modern +- intermediate +- mutex +- 异步编程 +title: condition_variable and Wait Semantics translation: + engine: anthropic source: documents/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md source_hash: 55180d919c132785b2b2ff56ff781820c74bc4d65ba0ea80520055cc1e8f4327 - translated_at: '2026-05-20T04:36:55.732540+00:00' - engine: anthropic token_count: 3161 + translated_at: '2026-05-20T04:36:55.732540+00:00' --- # condition_variable and Wait Semantics diff --git a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md index 6003271b0..47600cf20 100644 --- a/documents/en/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md +++ b/documents/en/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md @@ -1,31 +1,31 @@ --- -title: Read-Write Locks and shared_mutex -description: C++17 `shared_mutex` applications in read-heavy, write-light scenarios, - analyzing write starvation issues and performance boundaries chapter: 2 -order: 4 -tags: -- host -- cpp-modern -- intermediate -- mutex -difficulty: intermediate -platform: host -reading_time_minutes: 20 cpp_standard: - 17 - 20 +description: C++17 `shared_mutex` applications in read-heavy, write-light scenarios, + analyzing write starvation issues and performance boundaries +difficulty: intermediate +order: 4 +platform: host prerequisites: - condition_variable 与等待语义 +reading_time_minutes: 14 related: - mutex 与 RAII 锁 - 线程安全容器设计 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: Read-Write Locks and shared_mutex translation: + engine: anthropic source: documents/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md source_hash: 9b89015a3c8b2083856030f60c54f61b1695479e3d09b86dae1ec870e0149aa7 - translated_at: '2026-05-20T04:36:39.482406+00:00' - engine: anthropic token_count: 2330 + translated_at: '2026-05-20T04:36:39.482406+00:00' --- # Reader-Writer Locks and shared_mutex diff --git a/documents/en/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md b/documents/en/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md index b233be1cf..c31335f68 100644 --- a/documents/en/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md +++ b/documents/en/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md @@ -1,235 +1,172 @@ --- -title: atomic operation -description: 'The complete operation manual for `std::atomic`: load/store, fetch_add, - compare_exchange, and lock-free determination' chapter: 3 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- atomic -difficulty: intermediate -platform: host -reading_time_minutes: 22 cpp_standard: - 11 - 14 - 17 - 20 +description: 'Complete operation manual for std::atomic: load/store, fetch_add, + compare_exchange, and lock-free determination' +difficulty: intermediate +order: 1 +platform: host prerequisites: - latch、barrier 与 semaphore +reading_time_minutes: 18 related: - 内存序详解 - 原子操作模式 +tags: +- host +- cpp-modern +- intermediate +- atomic +title: atomic operation translation: - source: documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md - source_hash: 8200dcca123db20ed895454d3808df25e62d210aaeb28392b43a6f796d78106b - translated_at: '2026-05-26T11:44:54.314832+00:00' engine: anthropic - token_count: 4111 + source: documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md + source_hash: dc06bd976c7e03b3be88b94c28c35be66ac78a049e3ff700d91050ffdb32f196 + token_count: 4110 + translated_at: '2026-06-15T09:26:55.817508+00:00' --- -# atomic operation +# Atomic Operations -So far, the synchronization primitives we have discussed—mutex, condition variable, latch, barrier, semaphore—all follow the same fundamental pattern: lock, operate, then unlock. They are safe and intuitive, but they share a common cost: even if you only want to protect a simple integer increment, you still go through the full lock → modify → unlock cycle. For an operation as fine-grained as "modifying a single variable," this overhead feels disproportionate. +So far, the synchronization primitives we have discussed—mutex, condition variable, latch, barrier, and semaphore—essentially follow the "lock, operate, unlock" philosophy. They are safe and intuitive, but they share a common cost: even if you only want to protect a simple integer increment, you must go through the full lock → modify → unlock cycle. For operations with such fine granularity, like "modifying a variable," the weight of this process can feel mismatched. -`std::atomic` is designed for exactly these fine-grained scenarios. It does not rely on locks (at least ideally), but instead uses atomic instructions provided by the CPU to guarantee indivisible operations. In the previous chapter, we already used `std::atomic` to fix a data race when covering concurrency fundamentals, but we only scratched the surface. In this chapter, we will fully break down all operations of `std::atomic`—from the most basic `load`/`store`, to the CAS (compare-and-swap) mechanism, and finally to lock-free checks and the specialized type `atomic_flag`. We will discuss memory order in the next chapter; for now, let us focus purely on "what atomic operations can do." +`std::atomic` is designed specifically for these "fine-grained" scenarios. It does not rely on locks (at least ideally), but instead uses CPU-provided atomic instructions to guarantee that operations are indivisible. In the previous article, we used `std::atomic` to fix a data race in our discussion of basic concurrency issues, but we only scratched the surface. In this article, we will fully decompose all operations of `std::atomic`—from the most basic `load`/`store`, to the CAS (Compare-And-Swap) mechanism, and finally to lock-free determination and the specialized type `std::atomic_flag`. We will discuss memory ordering in the next article; for now, let's focus on "what atomic operations can do." -## What types does std::atomic support +## Which types does `std::atomic` support? -`std::atomic` is a class template defined in the `` header. Not all types can be placed into `std::atomic`—the standard places explicit restrictions on this. +`std::atomic` is a class template defined in the `` header file. Not all types can be used with `std::atomic`—the standard places explicit limits on this. -For integral types—`bool`, `char`, `short`, `int`, `long`, `long long`, and their unsigned variants—the standard library provides explicit specializations of `std::atomic` that support a full set of arithmetic and bitwise atomic operations (`fetch_add`, `fetch_sub`, `fetch_and`, `fetch_or`, `fetch_xor`). Pointer types are similarly specialized, supporting `fetch_add` and `fetch_sub` for atomically advancing pointers. +For integral types—`char`, `short`, `int`, `long`, `long long`, and their unsigned variants—the standard library provides explicit specializations of `std::atomic` that support full arithmetic and bitwise atomic operations (`fetch_add`, `fetch_sub`, `fetch_or`, `fetch_and`, `fetch_xor`). Pointer types are similarly specialized, supporting `fetch_add` and `fetch_sub` for atomically moving pointers. -For custom types `T`, `std::atomic` also exists, provided that `T` satisfies one core condition: `std::is_trivially_copyable::value` must be true—meaning `T` cannot have user-provided copy constructors/assignment operators (defaulted ones are fine), virtual functions, virtual base classes, and so on. Custom types meeting this condition can use the general-purpose operations `load()`, `store()`, `exchange()`, and `compare_exchange_weak/strong()`, but cannot use arithmetic operations like `fetch_add`—the standard has no obligation to define "addition" semantics for your custom types. +For custom types `T`, `std::atomic` also exists, provided `T` meets a core condition: `std::is_trivially_copyable` is true. This means `T` cannot have user-provided copy constructors/assignment (default ones are fine), virtual functions, virtual base classes, etc. Custom types meeting this condition can use generic operations like `load`, `store`, `exchange`, and `compare_exchange`, but cannot use arithmetic operations like `fetch_add`—the standard is not obligated to define "addition" semantics for your custom type. -Note that these general-purpose operations themselves impose additional requirements on `T`: `load()` requires `T` to be CopyConstructible, `store()` requires `T` to be CopyAssignable, and `exchange()` and `compare_exchange_*` require both. However, since `T` is trivially copyable, these requirements are almost always automatically satisfied. Additionally, the default constructor `std::atomic a;` value-initializes `T` prior to C++20 (so `T` must be default-constructible), but starting from C++20 it performs no initialization—if you use a parameterized constructor like `std::atomic a{T{...}};`, `T` does not need to be default-constructible. +Note that these generic operations have additional requirements on `T`. `load` requires `T` to be CopyConstructible, `store` requires `T` to be CopyAssignable, and `exchange` and `compare_exchange` require both. However, since `T` is trivially copyable, these requirements are almost always automatically met. Additionally, the default constructor `atomic()` performs value initialization on `T` prior to C++20 (requiring `T` to be default constructible), but since C++20 it leaves it uninitialized. If you use the parameterized constructor `atomic(T)`, `T` does not need to be default constructible. ```cpp -#include -#include - -// 整型:完全支持 -std::atomic atomic_int{0}; -std::atomic atomic_ulong{0}; - -// 指针:支持 fetch_add/fetch_sub(按元素大小偏移) -struct Node { - int value; - Node* next; +struct MyData { + int x, y; }; -std::atomic atomic_head{nullptr}; +static_assert(std::is_trivially_copyable_v); // Required for std::atomic -// 自定义 trivially-copyable 类型: -// 支持 load/store/exchange/CAS,但不支持 fetch_add -struct alignas(8) PacketHeader { - uint32_t id; - uint32_t flags; -}; -static_assert(std::is_trivially_copyable_v); -std::atomic atomic_header{PacketHeader{0, 0}}; +std::atomic data; +MyData local = data.load(); // OK +data.store({1, 2}); // OK +// data.fetch_add(...); // Error: MyData does not support arithmetic ``` -It is worth noting that starting from C++20, the standard explicitly supports `std::atomic` and `std::atomic`, and provides `fetch_add` and `fetch_sub` for floating-point specializations. Before C++20, however, floating-point atomic variables could only `load`, `store`, `exchange`, and `compare_exchange`—they could not perform atomic addition or subtraction directly. We will discuss the caveats of floating-point atomic operations in detail later. +It is worth noting that since C++20, the standard explicitly supports `std::atomic` and `std::atomic`, providing `fetch_add` and `fetch_sub` specializations for floating-point types. Before C++20, floating-point atomic variables could only be `load`, `store`, `exchange`, and `compare_exchange`—direct atomic addition/subtraction was not possible. We will discuss the caveats of floating-point atomic operations later. -## load() and store(): the foundation of atomic reads and writes +## `load()` and `store()`: The foundation of atomic read/write -`load()` and `store()` are the most fundamental pair of atomic operations. All atomic reads and writes ultimately boil down to these two operations (plus an optional memory order parameter). When no memory order is specified, all atomic operations default to `memory_order_seq_cst`—the strongest ordering guarantee. We will dive into the specific meanings of memory order in the next chapter; for now, just remember: the default parameter is safe, though not necessarily the fastest. +`load` and `store` are the most basic pair of atomic operations. All atomic reads and writes ultimately boil down to these two operations (plus an optional memory order parameter). If no memory order is specified, all atomic operations default to `std::memory_order_seq_cst`—the strongest ordering guarantee. We will expand on the specific meaning of memory orders in the next article; for now, just remember: the default parameters are safe, though not necessarily the fastest. ```cpp -#include -#include - -int main() -{ - std::atomic value{0}; - - // store:原子地写入一个值 - value.store(42); - value.store(100, std::memory_order_relaxed); - - // load:原子地读取当前值 - int x = value.load(); - int y = value.load(std::memory_order_relaxed); +std::atomic counter{0}; - // 便捷写法:隐式转换 - int z = value; // 等价于 value.load() - value = 200; // 等价于 value.store(200) +// Explicit load/store +int old_val = counter.load(std::memory_order_relaxed); +counter.store(old_val + 1, std::memory_order_relaxed); - std::cout << "value = " << value.load() << "\n"; - return 0; -} +// Implicit conversion (uses load) +int current = counter; ``` -Do not rush to use the convenient shorthand. `int z = value;` looks like an ordinary variable copy, but behind the scenes it performs an atomic load. Mixing implicit conversions in a complex expression can sometimes obscure the code's intent—is this a regular assignment or an atomic read? In team collaborations, we prefer explicitly calling `load()` and `store()`. Even though it requires a few more keystrokes, it makes it immediately obvious that we are operating on an atomic variable. +Don't rush to use the convenient shorthand just yet. While `int current = counter` looks like a normal variable copy, behind the scenes it is an atomic load. Mixing implicit conversions in complex expressions can sometimes obscure the code's intent—is this a normal assignment or an atomic read? In collaborative development, the author prefers explicitly calling `load` and `store`. While it requires typing a few more characters, it makes it immediately obvious that we are operating on an atomic variable. -## fetch_add, fetch_sub, and bitwise operations: atomic arithmetic +## `fetch_add`, `fetch_sub`, and bitwise operations: Atomic arithmetic -For integral and pointer types, `std::atomic` provides a family of fetch operations. They execute the entire read-modify-write (RMW) sequence of "read current value → perform operation → write back new value," and guarantee that this sequence is atomic—no intermediate state can be observed by other threads. +For integral and pointer types, `std::atomic` provides a set of `fetch` operations. They execute the "read current value → perform operation → write back new value" Read-Modify-Write (RMW) sequence, guaranteeing that this sequence is atomic—no intermediate state can be observed by other threads. -The return value of the fetch family of operations is the **old value before modification**, not the new value. This is a highly pragmatic design choice: returning the old value means you can accomplish both "reading the current state" and "modifying the state" in one step, which is extremely convenient when implementing lock-free algorithms. +The return value of `fetch` operations is the **old value** (before modification), not the new value. This is a very pragmatic design choice: returning the old value allows you to accomplish both "reading current state" and "modifying state" in one shot, which is extremely convenient when implementing lock-free algorithms. ```cpp -#include -#include - -int main() -{ - std::atomic counter{0}; - - // fetch_add:原子加法,返回旧值 - int old1 = counter.fetch_add(5); // counter 变成 5,old1 = 0 - int old2 = counter.fetch_add(3); // counter 变成 8,old2 = 5 - - // fetch_sub:原子减法,返回旧值 - int old3 = counter.fetch_sub(2); // counter 变成 6,old3 = 8 +std::atomic value{10}; - // 位运算 - counter.fetch_or(0xFF); // 按位或 - counter.fetch_and(0xF0); // 按位与 - counter.fetch_xor(0x0F); // 按位异或 +// Returns 10, value becomes 15 +int old = value.fetch_add(5); - std::cout << "counter = " << counter.load() << "\n"; - return 0; -} +// Returns 15, value becomes 10 +int old2 = value.fetch_sub(5); ``` -These operations also have corresponding compound assignment and increment/decrement operator overloads, but note that the operator overloads return the **new value** (specifically, the value after the operation is applied), not the old value—exactly the opposite of the fetch family: +These operations also have corresponding compound assignment and increment/decrement operator overloads, but note that the operator overloads return the **new value** (specifically, the value after the operation is applied), not the old value—this is the opposite of the `fetch` series: ```cpp -std::atomic x{10}; +std::atomic counter{0}; + +// Returns 1 (new value), counter becomes 1 +int result = ++counter; -// 运算符重载返回新值 -int new_val = ++x; // x 变成 11,new_val = 11 -int old_val = x++; // x 变成 12,old_val = 11(后置返回旧值) -x += 5; // x 变成 17 +// Returns 1 (old value), counter becomes 2 +int result2 = counter++; ``` -We want to emphasize a subtle detail that is easy to confuse: `x++` (post-increment) and `x.fetch_add(1)` do not behave identically. `x++` returns the value **before** the increment, which is indeed consistent with `fetch_add(1)`. However, `++x` (pre-increment) returns the value **after** the increment, which is equivalent to `x.fetch_add(1) + 1`. In scenarios where the return value is not needed (such as a pure increment counter), it does not matter which one you use; but if you use the return value in an expression, this distinction is critical. +I want to emphasize a confusing detail here: `counter++` (post-increment) and `counter.fetch_add(1)` do not have exactly the same effect. `counter++` returns the value **before** the increment, which is indeed consistent with `fetch_add(1)`. However, `++counter` (pre-increment) returns the value **after** the increment, which is equivalent to `counter.fetch_add(1) + 1`. In scenarios where the return value is not needed (e.g., a pure counter increment), it doesn't matter which one you use; but if you use the return value in an expression, this distinction is crucial. -## Caveats of floating-point atomic operations +## Caveats for floating-point atomic operations -This is a problem many people encounter the first time they use `std::atomic`. Starting from C++20, the floating-point specializations do indeed provide `fetch_add` and `fetch_sub`, but there are two layers of特殊性 to be aware of when using them. +This is a problem many encounter the first time they use `std::atomic`. While C++20 provides `fetch_add` and `fetch_sub` for floating-point specializations, there are two levels of specificity to be aware of. -At the hardware level, the vast majority of CPU architectures do not provide atomic floating-point addition instructions. x86 has `LOCK XADD` for integer atomic addition, but floating-point addition goes through the FPU/SSE/AVX execution units, which are not designed for atomic operations in the first place. Therefore, on most platforms, `atomic::fetch_add` internally degrades into a CAS loop—there is no hardware-level atomic floating-point addition. +At the hardware level, most CPU architectures do not provide atomic floating-point addition instructions. x86 has the `lock add` instruction for integer atomic addition, but floating-point addition goes through the FPU/SSE/AVX execution units, which are not designed for atomic operations in the first place. Therefore, `atomic::fetch_add` internally degrades into a CAS loop on most platforms—there is no hardware-level atomic floating-point addition. -At the semantic level, floating-point addition is not associative—`(a + b) + c` does not equal `a + (b + c)`, because each operation involves precision rounding. This means that even if multiple threads simultaneously perform `fetch_add` on a floating-point atomic variable, the final result depends on the execution order of the operations, and this order is nondeterministic. Furthermore, the results of floating-point operations may vary depending on the floating-point environment (rounding mode, precision control), which introduces additional non-reproducibility to the semantics of `fetch_add`. +At the semantic level, floating-point addition is not associative—`(a + b) + c` does not always equal `a + (b + c)` because each operation involves precision rounding. This means that even if multiple threads perform `fetch_add` on a floating-point atomic variable simultaneously, the final result depends on the execution order of the operations, and this order is non-deterministic. Furthermore, the results of floating-point operations may vary depending on the floating-point environment (rounding mode, precision control), bringing additional non-reproducibility to the semantics of `fetch_add`. -If you need to atomically modify a floating-point variable in a pre-C++20 environment, or if you need to avoid the precision non-reproducibility issues of `fetch_add`, the standard approach is to use a CAS loop: +If you need to atomically modify a floating-point variable in a pre-C++20 environment, or if you need to avoid the reproducibility issues of `fetch_add` precision, the standard approach is to use a CAS loop: ```cpp -#include - -std::atomic atomic_value{0.0f}; - -float atomic_fetch_add(float delta) -{ - float old_val = atomic_value.load(std::memory_order_relaxed); - float new_val; - do { - new_val = old_val + delta; - // 如果 atomic_value 还是 old_val,就把它换成 new_val - // 否则 old_val 被更新为当前值,重试 - } while (!atomic_value.compare_exchange_weak( - old_val, new_val, std::memory_order_relaxed)); - return old_val; +std::atomic shared_value{0.0}; + +void add_to_value(double delta) { + double expected = shared_value.load(); + while (!shared_value.compare_exchange_weak(expected, expected + delta)) { + // expected is updated by compare_exchange_weak on failure + } } ``` -We will see this pattern again shortly in the CAS section—it is the cornerstone of lock-free programming. +We will see this pattern again in the CAS section—it is the cornerstone of lock-free programming. -## compare_exchange_weak and compare_exchange_strong: the CAS mechanism +## `compare_exchange_weak` vs `compare_exchange_strong`: The CAS mechanism -Compare-And-Swap (CAS) is the most important primitive among atomic operations, bar none. Almost all lock-free data structure implementations are built on top of CAS. C++ provides two variants: `compare_exchange_weak` and `compare_exchange_strong`, and the difference between them is subtle but critical. +Compare-And-Swap (CAS) is the most important primitive in atomic operations, hands down. Almost all lock-free data structure implementations are built on CAS. C++ provides two variants: `compare_exchange_weak` and `compare_exchange_strong`, and their difference is subtle but critical. -Let us look at the interface first. Both have identical signatures: +Let's look at the interface. Both signatures are identical: ```cpp bool compare_exchange_weak(T& expected, T desired, - std::memory_order success = memory_order_seq_cst, - std::memory_order failure = memory_order_seq_cst); - -bool compare_exchange_strong(T& expected, T desired, - std::memory_order success = memory_order_seq_cst, - MemoryOrder failure = memory_order_seq_cst); + std::memory_order success = std::memory_order_seq_cst, + std::memory_order failure = std::memory_order_seq_cst); ``` -The execution logic is as follows: atomically compare the current value with `expected`. If they are equal, replace the current value with `desired` and return `true`; if they are not equal, load the current value into `expected` and return `false`. Note that on failure, `expected` is overwritten—this is an easily overlooked detail. If you need to use the original `expected` value afterward, remember to back it up in advance. +The execution logic is this: atomically compares the current value with `expected`. If they are equal, it replaces the current value with `desired` and returns `true`; if not equal, it loads the current value into `expected` and returns `false`. Note that on failure, `expected` is overwritten—this is an easily overlooked detail. If you need to use the original `expected` value later, remember to back it up. -The difference lies in "spurious failure": `compare_exchange_weak` may return `false` even when the current value equals `expected`. This is not a bug, but a hardware-level limitation. On architectures like ARM and PowerPC that implement CAS using LL/SC (Load-Linked/Store-Conditional) primitives, the SC instruction can fail for various reasons—another processor touched the same cache line, an interrupt occurred, or even a pure scheduling event. x86 uses the hardware `CMPXCHG` instruction and does not have this issue, so on x86, `weak` and `strong` generate identical code. +The difference lies in "spurious failure": `compare_exchange_weak` may return `false` even if the current value equals `expected`. This is not a bug, but a hardware limitation. On architectures like ARM and PowerPC that implement CAS using LL/SC (Load-Linked/Store-Conditional) primitives, the SC instruction may fail for various reasons—another processor touched the same cache line, an interrupt occurred, or even purely due to scheduling events. x86 uses the hardware `lock cmpxchg` instruction and does not have this problem, so on x86, `weak` and `strong` generate identical code. ```cpp -#include -#include - -int main() -{ - std::atomic value{10}; - - // CAS 成功的场景 - int expected = 10; - bool ok = value.compare_exchange_strong(expected, 20); - // ok = true, value = 20, expected 不变 - - // CAS 失败的场景 - expected = 10; // 重新设为 10 - ok = value.compare_exchange_strong(expected, 30); - // ok = false, value 仍为 20, expected 被更新为 20 - std::cout << "value = " << value.load() - << ", expected = " << expected << "\n"; - return 0; +std::atomic value{0}; +int expected = 0; + +// Weak version: May fail spuriously +while (!value.compare_exchange_weak(expected, 1)) { + // expected is updated to the current value on failure +} + +// Strong version: Only fails if values differ +while (!value.compare_exchange_strong(expected, 1)) { + // expected is updated to the current value on failure } ``` -When should you use `weak`, and when should you use `strong`? The rule is simple: if your CAS is already wrapped in a loop, use `weak`—a spurious failure just means one extra iteration, but `weak` saves the internal retry loop on LL/SC architectures, making it faster overall. If it is a one-shot CAS (not in a loop), use `strong`—otherwise, a single spurious failure could send your logic down the wrong branch. +When should you use `weak` vs `strong`? The rule is simple: if your CAS is already wrapped in a loop, use `weak`—a spurious failure just means one extra iteration, but `weak` avoids the internal retry loop on LL/SC architectures, making it faster overall. If you are doing a one-shot CAS (not in a loop), use `strong`—otherwise, a single spurious failure could send your logic down the wrong branch. ### Implementing a lock-free stack push with CAS -Let us look at a classic CAS application scenario—the push operation of a lock-free stack. This example nicely demonstrates the usage of `compare_exchange_weak` in a loop: +Let's look at a classic CAS application scenario—the push operation for a lock-free stack. This example demonstrates the usage of `compare_exchange_weak` in a loop: ```cpp -#include - struct Node { int data; Node* next; @@ -237,236 +174,155 @@ struct Node { std::atomic head{nullptr}; -void push(int value) -{ - Node* new_node = new Node{value, nullptr}; - - Node* old_head = head.load(std::memory_order_relaxed); - do { - new_node->next = old_head; - // 尝试把 head 从 old_head 换成 new_node - // 如果成功,push 完成 - // 如果失败(别人已经改了 head),old_head 被更新为最新值,重试 - } while (!head.compare_exchange_weak( - old_head, new_node, - std::memory_order_release, - std::memory_order_relaxed)); +void push(int new_data) { + Node* new_node = new Node{new_data, nullptr}; + + // new_node->next points to the current head + new_node->next = head.load(std::memory_order_relaxed); + + // If head is still what we think it is, swap it to new_node + while (!head.compare_exchange_weak(new_node->next, new_node, + std::memory_order_release, + std::memory_order_relaxed)) { + // If CAS fails, new_node->next is automatically updated + // to the current head. We just retry. + } } ``` -The logic of this code is: first read the current `head`, point the new node's `next` to it, then attempt to swap `head` with the new node via a single CAS. If another thread has already pushed a node (changing `head`) while we were preparing the new node, the CAS will fail, `old_head` will be updated to the latest `head`, and we reset `new_node->next` and try again. This process repeats until the CAS succeeds. +The logic here is: read the current `head`, point the new node's `next` to it, and then try to swap `head` to the new node with one CAS. If another thread pushes a node (changing `head`) while we are preparing the new node, the CAS fails, `new_node->next` is updated to the latest `head`, and we reset `new_node->next` and try again. This process repeats until CAS succeeds. -You may have noticed that `compare_exchange_weak` here accepts two memory order parameters: `success` and `failure`. On success, we use `memory_order_release` (because we just wrote a new node and need to ensure other threads can see the complete data); on failure, we use `memory_order_relaxed` (since we failed, no synchronization guarantees are needed—we are simply retrying). +You might notice that `compare_exchange_weak` here accepts two memory order parameters: `success` and `failure`. On success, we use `memory_order_release` (because we just wrote a new node and need to ensure other threads see the complete data). On failure, we use `memory_order_relaxed` (if it fails, no synchronization guarantees are needed, we are just retrying). -## exchange(): atomic swap +## `exchange()`: Atomic swap -`exchange()` is a relatively simple but highly practical operation: it atomically writes in a new value while taking out the old value. It is a combination of `load` and `store`, but guarantees that these two steps are indivisible. +`exchange` is a relatively simple but very practical operation: atomically writes a new value in while taking the old value out. It is a combination of `store` and `load`, but it guarantees that these two steps are indivisible. ```cpp -#include -#include - -int main() -{ - std::atomic flag{0}; - - int old = flag.exchange(1); - // 现在 flag = 1,old = 0 - std::cout << "flag = " << flag.load() - << ", old = " << old << "\n"; - return 0; -} +std::atomic status{0}; + +// Writes 1, returns the old value 0 +int old_status = status.exchange(1); ``` -A typical use case for `exchange()` is "state handoff"—atomically switching some state from A to B while deciding subsequent behavior based on the old state: +A typical use case for `exchange` is "state handover"—atomically switching a state from A to B while deciding subsequent behavior based on the old state: ```cpp -#include -#include - -enum class DeviceState { kIdle, kBusy, kError }; - -std::atomic state{DeviceState::kIdle}; - -void try_start_work() -{ - // 原子地尝试从 Idle 切换到 Busy - DeviceState old = state.exchange(DeviceState::kBusy); - if (old != DeviceState::kIdle) { - // 之前不是 Idle,说明有其他线程已经在用了 - // 恢复原状态(或者进入错误处理) - state.store(old); - std::cout << "Cannot start: device was " << - static_cast(old) << "\n"; - return; +enum State { Idle, Running, Stopped }; +std::atomic current_state{State::Idle}; + +void stop() { + // Switch to Stopped, check what state we were in + State prev = current_state.exchange(State::Stopped); + if (prev == State::Idle) { + // Was idle, cleanup not needed + } else if (prev == State::Running) { + // Was running, need cleanup } - // 成功切换到 Busy,开始工作 - std::cout << "Work started\n"; } ``` -Note that this example could actually be written more precisely with CAS (`exchange` unconditionally writes the new value even if the old state is not `kIdle`), but the advantage of `exchange` lies in its simplicity—if you just want to swap in a value and know what the old value was, `exchange` is much more concise than a CAS loop. +Note that this example could be written more precisely with CAS (`compare_exchange` checks the old state before swapping, whereas `exchange` swaps unconditionally even if the old state isn't what you expected). However, the advantage of `exchange` lies in its simplicity—if you just want to swap a value in and know what the old value was, `exchange` is much more concise than a CAS loop. -## is_lock_free and is_always_lock_free +## `is_lock_free` and `is_always_lock_free` -Up to this point we have been saying "atomic operations do not rely on locks," but the truth is not always so. Whether `std::atomic` is truly lock-free depends on two factors: the size of type `T` and the hardware capabilities of the target platform. If the hardware lacks atomic instructions of the corresponding width (for example, atomic operations on 64-bit integers on a 32-bit ARM), the compiler will fall back to using internal locks—at this point, operations on `std::atomic` are not truly lock-free. +We have been saying "atomic operations don't use locks," but that is not always the case. Whether `std::atomic` is truly lock-free depends on two factors: the size of type `T` and the hardware capabilities of the target platform. If the hardware lacks atomic instructions of the corresponding width (e.g., atomic operations on 64-bit integers on 32-bit ARM), the compiler will settle for the next best thing: implementing it with internal locks. In this case, `std::atomic` operations are not truly lock-free. -The standard library provides two interfaces to query this. `is_lock_free()` is a runtime query that returns `true` indicating that operations on the current object are lock-free. `is_always_lock_free` is a compile-time constant (`static constexpr`) that returns `true` indicating that atomic operations of this type are lock-free for **all** instances on that platform. If you need to make a static assertion at compile time, use `is_always_lock_free`; if you need to make a runtime branch decision, use `is_lock_free()`. +The standard library provides two interfaces to query this. `is_lock_free()` is a runtime query returning `true` if operations on the current object are lock-free. `is_always_lock_free` is a compile-time constant (`constexpr`) returning `true` if atomic operations of this type are lock-free for **all** instances on this platform. If you need to make a static assertion at compile time, use `is_always_lock_free`; if you need to make a branch judgment at runtime, use `is_lock_free()`. ```cpp -#include -#include - -int main() -{ - std::atomic ai; - std::atomic all; - - std::cout << "atomic: " - << (ai.is_lock_free() ? "lock-free" : "uses lock") - << "\n"; - std::cout << "atomic: " - << (all.is_lock_free() ? "lock-free" : "uses lock") - << "\n"; - - // 编译期检查:如果 int 不是 lock-free 的,直接编译报错 - static_assert(std::atomic::is_always_lock_free, - "int must be lock-free on this platform!"); - - return 0; +std::atomic int_atom; +std::atomic ll_atom; + +if (int_atom.is_lock_free()) { + // int operations are lock-free at runtime +} + +if constexpr (std::atomic::is_always_lock_free) { + // long long operations are guaranteed lock-free at compile time } ``` -In real-world projects, `is_always_lock_free` is more valuable than `is_lock_free()`. The reason is: if your code path has branches depending on the return value of `is_lock_free()`, it means the same code might take different paths on different running instances—this is a nightmare for testing and debugging. In contrast, `static_assert` + `is_always_lock_free` can expose the problem at compile time: either the platform fully supports lock-free operations, or the code fails to compile—there is no gray area. +In actual projects, `is_always_lock_free` is more valuable than `is_lock_free()`. The reason is: if your code path branches based on the return value of `is_lock_free()`, it means the same code might take different paths on different runtime instances—a nightmare for testing and debugging. In contrast, `static_assert` + `is_always_lock_free` exposes the problem at compile time: either the platform fully supports lock-free, or the code fails to compile, leaving no gray area. -In embedded scenarios, this is especially important. On 32-bit ARM Cortex-M, `std::atomic` is almost always lock-free (the hardware has the `LDREX`/`STREX` instruction pair), but `std::atomic` may not be on Cortex-M0/M3. If you use atomic operations in an ISR, you must confirm that they are lock-free—an ISR cannot block, and lock-based atomic operations will block. +In embedded scenarios, this is particularly important. On 32-bit ARM Cortex-M, `std::atomic` is almost always lock-free (hardware has `LDREX`/`STREX` instruction pairs), but `std::atomic` may not be on Cortex-M0/M3. If you use atomic operations in an ISR, make sure they are lock-free—ISRs cannot block, and lock-based atomic operations will block. -## atomic_flag: the standard-guaranteed lock-free primitive +## `atomic_flag`: The standard-guaranteed lock-free primitive -Whether `std::atomic` is lock-free depends on the platform, but `std::atomic_flag` is an exception—the standard guarantees that `std::atomic_flag` is **always lock-free**. On all platforms, with all compilers, without exception. This makes `atomic_flag` the most reliable cornerstone for building low-level synchronization primitives (such as spinlocks). +Whether `std::atomic` is lock-free depends on the platform, but `std::atomic_flag` is an exception—the standard guarantees that `std::atomic_flag` **is always lock-free**. On all platforms, with all compilers, without exception. This makes `std::atomic_flag` the most reliable cornerstone for building low-level synchronization primitives (like spinlocks). -`atomic_flag` has only two states: set (true) and clear (false). It provides three core operations: `test_and_set()` atomically sets the flag to true and returns the previous value; `clear()` atomically sets the flag to false; and C++20 adds `test()` for atomically reading the current value without modifying it. +`std::atomic_flag` has only two states: set (true) and clear (false). It provides three core operations: `test_and_set` atomically sets the flag to true and returns the previous value; `clear` atomically sets the flag to false; and C++20 added `test` for atomically reading the current value without modifying it. ```cpp -#include -#include +std::atomic_flag flag = ATOMIC_FLAG_INIT; // Initialize to clear -int main() -{ - // C++20 起可以直接 {} 初始化 - std::atomic_flag flag{}; +// Set to true, return previous value (false) +bool was_set = flag.test_and_set(); - // test_and_set:设置为 true,返回旧值 - bool was_set = flag.test_and_set(); - std::cout << "was_set = " << std::boolalpha << was_set << "\n"; +// Set to false +flag.clear(); - // test(C++20):读取当前值 - bool current = flag.test(); - std::cout << "current = " << current << "\n"; - - // clear:设置为 false - flag.clear(); - std::cout << "after clear: " << flag.test() << "\n"; - - return 0; -} +// C++20: Read current value +bool is_set = flag.test(); ``` -### Implementing a spinlock with atomic_flag +### Implementing a spinlock with `atomic_flag` -The most classic application of `atomic_flag` is the spinlock. The principle of a spinlock is simple: when acquiring the lock, continuously attempt `test_and_set`; if it returns false (previously in the clear state), it means we successfully acquired the lock; if it returns true (previously in the set state), it means the lock is held by someone else, so we spin again. When releasing the lock, call `clear`. +The most classic application of `std::atomic_flag` is a spinlock. The principle of a spinlock is simple: when acquiring the lock, keep trying `test_and_set`. If it returns false (was in clear state), you successfully acquired the lock; if it returns true (was already in set state), the lock is held by someone else, so keep spinning. When releasing the lock, call `clear`. ```cpp -#include -#include -#include -#include - class SpinLock { + std::atomic_flag flag = ATOMIC_FLAG_INIT; public: - void lock() - { - while (flag_.test_and_set(std::memory_order_acquire)) { - // 自旋等待:CPU 在空转 - // 在 x86 上可以插入 _mm_pause() 降低功耗 - // 在 ARM 上可以插入 __yield() + void lock() { + // Spin until we successfully set the flag from false to true + while (flag.test_and_set(std::memory_order_acquire)) { + // Optional: CPU pause hint (e.g., _mm_pause() on x86) } } - - void unlock() - { - flag_.clear(std::memory_order_release); + void unlock() { + flag.clear(std::memory_order_release); } - -private: - std::atomic_flag flag_{}; }; - -// 使用示例 -SpinLock spinlock; -int shared_counter = 0; - -void increment(int times) -{ - for (int i = 0; i < times; ++i) { - spinlock.lock(); - ++shared_counter; - spinlock.unlock(); - } -} - -int main() -{ - std::vector threads; - for (int i = 0; i < 4; ++i) { - threads.emplace_back(increment, 250000); - } - for (auto& t : threads) { - t.join(); - } - std::cout << "shared_counter = " << shared_counter << "\n"; - // 输出:shared_counter = 1000000 - return 0; -} ``` -The drawback of a spinlock is obvious: while the lock is held, other threads are spinning idly, wasting CPU time. Therefore, spinlocks are only suitable for scenarios with very short critical sections—ideally, the lock should be held for such a short time that "another thread has not even had a chance to be scheduled away before it is released." If the critical section is relatively long, using `std::mutex` (an OS-level blocking lock) is more appropriate. +The downside of a spinlock is obvious: other threads are spinning while the lock is held, wasting CPU time in vain. Therefore, spinlocks are only suitable for scenarios with extremely short critical sections—ideally, the lock hold time should be so short that "the other thread hasn't had time to be scheduled away before it's released." If the critical section is relatively long, `std::mutex` (an OS-level blocking lock) is more appropriate. -C++20 also adds `wait()` and `notify_one()`/`notify_all()` operations for `atomic_flag`, allowing the spinlock to evolve into a more efficient "wait lock"—instead of spinning idly on acquisition failure, the thread is suspended and woken up when the lock is released. Under the hood, it uses `futex` on Linux and `WaitOnAddress` on Windows, saving far more CPU than pure spinning. +C++20 also added `wait` and `notify`/`notify_one` operations to `atomic_flag`, allowing the spinlock to evolve into a more efficient "wait lock"—instead of spinning when acquisition fails, the thread is suspended and woken up when the lock is released. Under the hood, it uses `futex` on Linux and `WaitOnAddress` on Windows, saving much more CPU than pure spinning. ## Common misconceptions -Before we wrap up, let us quickly go over a few easy-to-fall-into traps. +Before we wrap up, let's quickly go over a few common pitfalls. -The first misconception: assuming that atomic variables can solve all race conditions. Atomic operations guarantee the atomicity of a **single access**, but they do not guarantee atomicity **across multiple atomic operations**. For example: +The first misconception: thinking atomic variables solve all race conditions. Atomic operations guarantee the atomicity of a **single access**, but they do not guarantee atomicity **between multiple atomic operations**. For example: ```cpp std::atomic x{0}; std::atomic y{0}; -// 线程 1 -x.store(1); -y.store(2); +// Thread 1 +x.store(1, std::memory_order_relaxed); +y.store(1, std::memory_order_relaxed); -// 线程 2 -int a = y.load(); -int b = x.load(); +// Thread 2 +int r1 = y.load(std::memory_order_relaxed); // Might see 1 +int r2 = x.load(std::memory_order_relaxed); // Might see 0 ``` -Even though the individual `load`/`store` operations of `x` and `y` are each atomic, thread 2 might still see `a == 2` but `b == 0`—because there is no synchronization relationship between the two `store` operations or between the two `load` operations. This is not something atomic operations can solve; it requires memory order constraints. We will explore this topic in detail in the next chapter. +Even though `x` and `y`'s individual `store`/`load` are atomic, Thread 2 might still see `y` as 1 but `x` as 0—because there is no synchronization relationship between the two `store`s or between the two `load`s. This is not something atomic operations can solve; it requires memory ordering to constrain. We will expand on this topic in the next article. -The second misconception: believing that `volatile` is equivalent to `std::atomic`. The semantics of `volatile` are "do not optimize away accesses to this variable"—every read and write will truly access memory without caching. But `volatile` **does not guarantee atomicity, nor does it guarantee memory order**. A `++counter` on `volatile int counter;` is still a three-step read-modify-write operation and will still result in a data race. The original design intent of `volatile` was for hardware register mapping and signal handlers, not for multithreading. +The second misconception: thinking `volatile` is equivalent to `std::atomic`. The semantics of `volatile` are "do not optimize accesses to this variable"—every read/write actually touches memory, no caching. However, `volatile` **guarantees neither atomicity nor memory ordering**. `++` on a `volatile int` is still a three-step read-modify-write operation and can still have a data race. `volatile` was designed for memory-mapped hardware registers and signal handlers, not for multithreading. -The third misconception: using `std::atomic` on a non-trivially-copyable type like `std::atomic`. The standard does not allow this—the compiler will report an error directly. `std::string` has a user-defined copy constructor (involving heap memory allocation internally) and does not satisfy the trivially copyable requirement. If you need to share a string atomically, you can use `std::atomic>` (supported starting from C++20) or protect it with a mutex. +The third misconception: using `std::atomic` on non-trivially-copyable types like `std::string`. The standard does not allow this—the compiler will error out directly. `std::string` has user-defined copy constructors (involving heap memory allocation internally) and does not meet the trivially copyable requirement. If you need to share strings atomically, use `std::atomic>` (supported since C++20) or protect it with a mutex. -## Run online +## Run Online -Experience atomic load/store, fetch_add, compare_exchange, and atomic_flag spinlock primitives online: +Experience atomic `load`/`store`, `fetch_add`, `compare_exchange`, and `atomic_flag` spinlock primitives online: @@ -475,49 +331,40 @@ Experience atomic load/store, fetch_add, compare_exchange, and atomic_flag spinl ### Exercise 1: Lock-free counter -Implement a multithread-safe counter using `std::atomic`. Launch eight threads, each incrementing the counter 100,000 times, and the final result should be 800,000. Test both implementations using `fetch_add` and a `compare_exchange_weak` loop, and compare their correctness and performance differences. +Implement a multithread-safe counter using `std::atomic`. Requirements: Start 8 threads, each incrementing the counter 100,000 times. The final result should be 800,000. Test both `fetch_add` and a `compare_exchange` loop implementation, and compare their correctness and performance differences. -Hint: The approach to implementing `fetch_add` with `compare_exchange_weak` is—read the current value, calculate the new value, attempt to replace it with CAS, and retry on failure. +**Hint:** The idea of using `compare_exchange` to implement `fetch_add` is—read the current value, calculate the new value, try to replace with CAS, and retry on failure. -### Exercise 2: Lock-free maximum value tracker +### Exercise 2: Lock-free maximum tracker -Implement a thread-safe maximum value tracker: multiple threads continuously write random values, and the tracker always records the maximum value among all written values. You must use `compare_exchange_strong` (not `fetch_add`) to implement this. +Implement a thread-safe maximum tracker: multiple threads continuously write random values, and the tracker always records the maximum value among all written values. Requirements: Use `compare_exchange_strong` (not `compare_exchange_weak`). -Hint: The `expected` parameter of `compare_exchange_strong` is updated to the current value on failure—you need to compare the current value with your candidate new value in this "failure" branch to decide whether to retry. +**Hint:** The `expected` parameter of `compare_exchange_strong` is updated to the current value on failure—you need to compare this current value with your candidate new value in this "failure" branch to decide whether a retry is needed. ```cpp class MaxTracker { + std::atomic max_val; public: - void update(int new_value) - { - int current = max_.load(std::memory_order_relaxed); - while (new_value > current) { - if (max_.compare_exchange_strong( - current, new_value, std::memory_order_relaxed)) { - break; // 成功更新 - } - // 失败:current 被更新为最新值,继续比较 - } - } + MaxTracker() : max_val(0) {} - int get() const - { - return max_.load(std::memory_order_relaxed); + void update(int candidate) { + // TODO: Implement this } -private: - std::atomic max_{std::numeric_limits::min()}; + int get_max() const { + return max_val.load(); + } }; ``` -After completing the `update` function above, test it with multiple threads: create eight threads, each generating 100,000 random values and calling `update`, then verify that the value returned by `get()` is indeed the maximum among all values generated by the threads. +After completing the `update` function above, test it with multiple threads: create 8 threads, each generating 100,000 random values and calling `update`, and finally verify that `get_max` returns the maximum value among all generated values. -> 💡 Complete example code is available in [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `code/volumn_codes/vol5/ch03-atomic-memory-model/`. +> 💡 Complete example code is available at [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP), visit `code/examples/vol5/11_atomic.cpp`. ## References - [std::atomic -- cppreference](https://en.cppreference.com/w/cpp/atomic/atomic) - [std::atomic_flag -- cppreference](https://en.cppreference.com/w/cpp/atomic/atomic_flag) - [compare_exchange_weak vs compare_exchange_strong -- cppreference](https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange) -- [C++ Concurrency in Action, 2nd Edition -- Anthony Williams](https://www.cplusplus.com/reference/atomic/atomic/) +- [C++ Concurrency in Action, 2nd Edition -- Anthony Williams](https://wwwcpluspluscom/reference/atomic/atomic/) - [atomic is_lock_free -- cppreference](https://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free) diff --git a/documents/en/vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md b/documents/en/vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md index cade5c780..5d9907a86 100644 --- a/documents/en/vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md +++ b/documents/en/vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md @@ -1,34 +1,34 @@ --- -title: Memory Order Explained -description: From compiler reordering to CPU reordering, breaking down the six `memory_order` - values and the happens-before relationship one by one. chapter: 3 -order: 2 -tags: -- host -- cpp-modern -- advanced -- atomic -- memory_order -difficulty: advanced -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: From compiler reordering to CPU reordering, breaking down the six `memory_order` + values and the happens-before relationship one by one. +difficulty: advanced +order: 2 +platform: host prerequisites: - atomic 操作 +reading_time_minutes: 16 related: - fence 与编译器屏障 - 原子操作模式 +tags: +- host +- cpp-modern +- advanced +- atomic +- memory_order +title: Memory Order Explained translation: + engine: anthropic source: documents/vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md source_hash: daf000fe389a45fe175cafa1f72f5dafcd40f2b83bb51d0dc03260d73dfe648b - translated_at: '2026-05-20T04:38:31.241349+00:00' - engine: anthropic token_count: 2916 + translated_at: '2026-05-20T04:38:31.241349+00:00' --- # Memory Order Explained diff --git a/documents/en/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md b/documents/en/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md index e85061c97..876a20b10 100644 --- a/documents/en/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md +++ b/documents/en/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md @@ -1,34 +1,34 @@ --- -title: Atomic Operation Memory Order -description: Correct implementation of classic atomic patterns such as SeqLock, Double-Checked - Locking, reference counting, and publish-subscribe. chapter: 3 -order: 5 -tags: -- host -- cpp-modern -- advanced -- atomic -- 无锁 -difficulty: advanced -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Correct implementation of classic atomic patterns such as SeqLock, Double-Checked + Locking, reference counting, and publish-subscribe. +difficulty: advanced +order: 5 +platform: host prerequisites: - fence 与编译器屏障 - atomic_wait 与 atomic_ref +reading_time_minutes: 22 related: - 无锁编程基础 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +title: Atomic Operation Memory Order translation: + engine: anthropic source: documents/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md source_hash: 6a16ee5ae8b32d406353bc2afbd7dc091077f2bcf1b3ac8dbdc6599198b87cc4 - translated_at: '2026-06-13T11:51:22.489438+00:00' - engine: anthropic token_count: 5394 + translated_at: '2026-06-13T11:51:22.489438+00:00' --- # Atomic Operation Patterns diff --git a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md index c643d1014..5ded9b218 100644 --- a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md +++ b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md @@ -1,33 +1,33 @@ --- -title: Thread-Safe Queue -description: Building a closeable, timeout-supporting bounded blocking queue with - `mutex` + `condition_variable` chapter: 4 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- mutex -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Building a closeable, timeout-supporting bounded blocking queue with + `mutex` + `condition_variable` +difficulty: intermediate +order: 1 +platform: host prerequisites: - condition_variable 与等待语义 +reading_time_minutes: 26 related: - 线程安全容器设计 - SPSC 与 MPMC 队列 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: Thread-Safe Queue translation: + engine: anthropic source: documents/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md source_hash: 1fa1f6a0bfae90d0f8b6e903d234908048aab0502d588fac75059a8d1322e184 - translated_at: '2026-05-20T04:41:50.333341+00:00' - engine: anthropic token_count: 5320 + translated_at: '2026-05-20T04:41:50.333341+00:00' --- # Thread-Safe Queues diff --git a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md index 9ec55e3be..197d03468 100644 --- a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md +++ b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md @@ -1,34 +1,34 @@ --- -title: Thread-Safe Container Design -description: 'Design and trade-offs of four strategies: coarse-grained locks, fine-grained - locks, sharded locks, and copy-on-write' chapter: 4 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- mutex -- 容器 -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: 'Design and trade-offs of four strategies: coarse-grained locks, fine-grained + locks, sharded locks, and copy-on-write' +difficulty: intermediate +order: 2 +platform: host prerequisites: - 线程安全队列 - 读写锁与 shared_mutex +reading_time_minutes: 24 related: - 无锁编程基础 +tags: +- host +- cpp-modern +- intermediate +- mutex +- 容器 +title: Thread-Safe Container Design translation: + engine: anthropic source: documents/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md source_hash: 9aa5dfb9e73a85705d8a82e3f06b2d70afa586cf371d68b5a9a6ee9b349428e2 - translated_at: '2026-05-20T04:40:43.578777+00:00' - engine: anthropic token_count: 4007 + translated_at: '2026-05-20T04:40:43.578777+00:00' --- # Thread-Safe Container Design diff --git a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md index f1c2ee033..33f038b03 100644 --- a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md +++ b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md @@ -1,33 +1,33 @@ --- -title: Lock-Free Programming Fundamentals -description: CAS loops, lock-free vs. wait-free, the ABA problem, and memory reclamation - challenges—building foundational judgment for lock-free programming. chapter: 4 -order: 3 -tags: -- host -- cpp-modern -- advanced -- atomic -- 无锁 -difficulty: advanced -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: CAS loops, lock-free vs. wait-free, the ABA problem, and memory reclamation + challenges—building foundational judgment for lock-free programming. +difficulty: advanced +order: 3 +platform: host prerequisites: - 原子操作模式 +reading_time_minutes: 29 related: - SPSC 与 MPMC 队列 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +title: Lock-Free Programming Fundamentals translation: + engine: anthropic source: documents/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md source_hash: 8bc0fe05876e6efe7565af0e9169a74089ffe28ab7016505920ed17fc98e20e5 - translated_at: '2026-05-20T04:41:18.899171+00:00' - engine: anthropic token_count: 4442 + translated_at: '2026-05-20T04:41:18.899171+00:00' --- # Lock-Free Programming Fundamentals diff --git a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md index 83968091a..342680d73 100644 --- a/documents/en/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md +++ b/documents/en/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md @@ -1,35 +1,35 @@ --- -title: SPSC and MPMC Queues -description: From ring buffer SPSC to Michael-Scott MPMC queues, cache-friendly producer-consumer - queue design chapter: 4 -order: 4 -tags: -- host -- cpp-modern -- advanced -- atomic -- 无锁 -- 循环缓冲区 -difficulty: advanced -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: From ring buffer SPSC to Michael-Scott MPMC queues, cache-friendly producer-consumer + queue design +difficulty: advanced +order: 4 +platform: host prerequisites: - 无锁编程基础 +reading_time_minutes: 26 related: - 线程安全队列 - 线程池设计 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +- 循环缓冲区 +title: SPSC and MPMC Queues translation: + engine: anthropic source: documents/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md source_hash: 93b8e3696584cb9feffefabb4e6f6400c2d6939ae465023f98c8c1b900458246 - translated_at: '2026-05-20T04:41:56.714493+00:00' - engine: anthropic token_count: 5461 + translated_at: '2026-05-20T04:41:56.714493+00:00' --- # SPSC and MPMC Queues diff --git a/documents/en/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md b/documents/en/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md index 46e177629..258da2486 100644 --- a/documents/en/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md +++ b/documents/en/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md @@ -1,33 +1,33 @@ --- -title: std::async and future -description: Understanding `std::async` launch policies, the blocking semantics of - `future.get`, and the deferred trap chapter: 5 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- 异步编程 -difficulty: intermediate -platform: host -reading_time_minutes: 20 cpp_standard: - 11 - 14 - 17 - 20 +description: Understanding `std::async` launch policies, the blocking semantics of + `future.get`, and the deferred trap +difficulty: intermediate +order: 1 +platform: host prerequisites: - 线程安全队列 +reading_time_minutes: 24 related: - promise 与 packaged_task - 线程池设计 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +title: std::async and future translation: + engine: anthropic source: documents/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md source_hash: 31367c94a78e8403d9b1a3b9d7e670ce34f2159b63a0a77d59561e4f4e80b375 - translated_at: '2026-05-20T04:42:48.216328+00:00' - engine: anthropic token_count: 4366 + translated_at: '2026-05-20T04:42:48.216328+00:00' --- # std::async and future diff --git a/documents/en/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md b/documents/en/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md index 697378bca..9aa59ec6d 100644 --- a/documents/en/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md +++ b/documents/en/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md @@ -1,33 +1,33 @@ --- -title: promise and packaged_task -description: Manually setting the value and exception of a future, wrapping callable - objects with `packaged_task`, and building flexible task channels chapter: 5 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- 异步编程 -difficulty: intermediate -platform: host -reading_time_minutes: 20 cpp_standard: - 11 - 14 - 17 - 20 +description: Manually setting the value and exception of a future, wrapping callable + objects with `packaged_task`, and building flexible task channels +difficulty: intermediate +order: 2 +platform: host prerequisites: - std::async 与 future +reading_time_minutes: 23 related: - jthread 与停止令牌 - 线程池设计 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +title: promise and packaged_task translation: + engine: anthropic source: documents/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md source_hash: 45a6a89f7574e65be996f0c97cd3fa9065261706bc6c4fb38670218a9583dd9f - translated_at: '2026-05-26T11:44:00.498667+00:00' - engine: anthropic token_count: 4646 + translated_at: '2026-05-26T11:44:00.498667+00:00' --- # promise and packaged_task diff --git a/documents/en/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md b/documents/en/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md index eae9fe61b..a7df0240b 100644 --- a/documents/en/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md +++ b/documents/en/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md @@ -1,9 +1,18 @@ --- -title: jthread and Stop Tokens +chapter: 5 +cpp_standard: +- 20 description: 'C++20 auto-joining threads and cooperative cancellation: complete usage of `stop_source`, `stop_token`, and `stop_callback`' -chapter: 5 +difficulty: intermediate order: 3 +platform: host +prerequisites: +- promise 与 packaged_task +reading_time_minutes: 19 +related: +- 线程所有权与 RAII +- 线程池设计 tags: - host - cpp-modern @@ -11,22 +20,13 @@ tags: - 异步编程 - RAII守卫 - 进阶 -difficulty: intermediate -platform: host -reading_time_minutes: 22 -cpp_standard: -- 20 -prerequisites: -- promise 与 packaged_task -related: -- 线程所有权与 RAII -- 线程池设计 +title: jthread and Stop Tokens translation: + engine: anthropic source: documents/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md source_hash: 4c2a8ac69cbe613ec907a2cd164e203e962daebcb03fd9f3d30c2c4a483da809 - translated_at: '2026-05-20T04:43:14.571815+00:00' - engine: anthropic token_count: 3922 + translated_at: '2026-05-20T04:43:14.571815+00:00' --- # jthread and Stop Tokens diff --git a/documents/en/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md b/documents/en/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md index fa0ecb9ce..3f8b4fa6f 100644 --- a/documents/en/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md +++ b/documents/en/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md @@ -1,36 +1,36 @@ --- -title: Thread Pool Design -description: Starting from a worker, task queue, and `condition_variable`, we build - a thread pool that supports `future` returns, exception propagation, and graceful - shutdown. chapter: 5 -order: 4 -tags: -- host -- cpp-modern -- advanced -- 异步编程 -- mutex -difficulty: advanced -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Starting from a worker, task queue, and `condition_variable`, we build + a thread pool that supports `future` returns, exception propagation, and graceful + shutdown. +difficulty: advanced +order: 4 +platform: host prerequisites: - jthread 与停止令牌 - promise 与 packaged_task +reading_time_minutes: 34 related: - 线程安全队列 - std::async 与 future +tags: +- host +- cpp-modern +- advanced +- 异步编程 +- mutex +title: Thread Pool Design translation: + engine: anthropic source: documents/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md source_hash: 5c05ded33db6e734648e2ea6af2aa67c24671c415aaaf22427eee02526695729 - translated_at: '2026-05-20T04:44:28.015432+00:00' - engine: anthropic token_count: 6911 + translated_at: '2026-05-20T04:44:28.015432+00:00' --- # Thread Pool Design diff --git a/documents/en/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md b/documents/en/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md index c9162d037..5018c3107 100644 --- a/documents/en/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md +++ b/documents/en/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md @@ -1,36 +1,36 @@ --- -title: 'Asynchronous Programming Evolution: From Callback Hell to Coroutines' -description: Tracing the evolution of asynchronous programming paradigms—callbacks, - future chains, and coroutines—to understand the motivation, pain points, and implementation - forms of each model in C++. chapter: 6 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- 异步编程 -- 基础 -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Tracing the evolution of asynchronous programming paradigms—callbacks, + future chains, and coroutines—to understand the motivation, pain points, and implementation + forms of each model in C++. +difficulty: intermediate +order: 1 +platform: host prerequisites: - 线程池设计 - promise 与 packaged_task +reading_time_minutes: 18 related: - C++20 协程基础 - 异步 I/O 与事件循环 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +- 基础 +title: 'Asynchronous Programming Evolution: From Callback Hell to Coroutines' translation: + engine: anthropic source: documents/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md source_hash: 69bdb786dac2ba9a89659ceb3dbc19b6a9c686db20dd2b1800f19ad72d3bf599 - translated_at: '2026-06-13T11:51:48.247704+00:00' - engine: anthropic token_count: 3751 + translated_at: '2026-06-13T11:51:48.247704+00:00' --- # Evolution of Asynchronous Programming: From Callback Hell to Coroutines diff --git a/documents/en/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md b/documents/en/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md index 13b7db2d7..8a9b9512a 100644 --- a/documents/en/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md +++ b/documents/en/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md @@ -1,32 +1,32 @@ --- -title: C++20 Coroutine Fundamentals +chapter: 6 +cpp_standard: +- 20 description: Dive deep into C++20 coroutine syntax, state machine models, and lifecycle management, and understand the compiler transformations behind `co_await`, `co_yield`, and `co_return`. -chapter: 6 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- coroutine -- 异步编程 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 30 -cpp_standard: -- 20 prerequisites: - 异步编程演进:从回调地狱到协程 +reading_time_minutes: 25 related: - promise_type 与 awaitable - 异步 I/O 与事件循环 +tags: +- host +- cpp-modern +- intermediate +- coroutine +- 异步编程 +title: C++20 Coroutine Fundamentals translation: + engine: anthropic source: documents/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md source_hash: ffe072d22553156ceee0efbae135d04cf3f717b498821b61c92c771e8d118899 - translated_at: '2026-05-20T04:44:59.439012+00:00' - engine: anthropic token_count: 5417 + translated_at: '2026-05-20T04:44:59.439012+00:00' --- # C++20 Coroutine Basics diff --git a/documents/en/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md b/documents/en/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md index 38435d913..a2db51fe5 100644 --- a/documents/en/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md +++ b/documents/en/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md @@ -1,31 +1,31 @@ --- -title: promise_type and awaitable +chapter: 6 +cpp_standard: +- 20 description: Master the two major customization extension points of C++20 coroutines—`promise_type` controls coroutine behavior, while awaitable controls suspension and resumption. -chapter: 6 -order: 3 -tags: -- host -- cpp-modern -- advanced -- coroutine -- 异步编程 difficulty: advanced +order: 3 platform: host -reading_time_minutes: 30 -cpp_standard: -- 20 prerequisites: - C++20 协程基础 +reading_time_minutes: 28 related: - 异步 I/O 与事件循环 - 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- advanced +- coroutine +- 异步编程 +title: promise_type and awaitable translation: + engine: anthropic source: documents/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md source_hash: 7e640814d8bf6b9d34b83e4824465ebecdf8cc0017be5b5da2fed0cf4bc85799 - translated_at: '2026-05-20T04:46:59.715535+00:00' - engine: anthropic token_count: 5943 + translated_at: '2026-05-20T04:46:59.715535+00:00' --- # promise_type and awaitable diff --git a/documents/en/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md b/documents/en/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md index 9651f1c77..c363a9af2 100644 --- a/documents/en/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md +++ b/documents/en/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md @@ -1,31 +1,31 @@ --- -title: Asynchronous I/O and Event Loops +chapter: 6 +cpp_standard: +- 20 description: Understand how I/O multiplexing (epoll/io_uring) works, build a coroutine-driven event loop, and bridge the final gap in asynchronous I/O. -chapter: 6 -order: 4 -tags: -- host -- cpp-modern -- advanced -- coroutine -- 异步编程 difficulty: advanced +order: 4 platform: host -reading_time_minutes: 35 -cpp_standard: -- 20 prerequisites: - promise_type 与 awaitable - CPU cache 与 OS 线程 +reading_time_minutes: 25 related: - 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- advanced +- coroutine +- 异步编程 +title: Asynchronous I/O and Event Loops translation: + engine: anthropic source: documents/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md source_hash: ef24e2f38eeb3caece0731d0e89703097248a52b4c2cc64ad93c54ee1acc49b7 - translated_at: '2026-05-20T04:47:00.232107+00:00' - engine: anthropic token_count: 4825 + translated_at: '2026-05-20T04:47:00.232107+00:00' --- # Asynchronous I/O and the Event Loop diff --git a/documents/en/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md b/documents/en/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md index 19a895f21..7b53d1453 100644 --- a/documents/en/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md +++ b/documents/en/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md @@ -1,9 +1,18 @@ --- -title: 'Hands-on: Coroutine Echo Server' +chapter: 6 +cpp_standard: +- 20 description: Implementing a complete TCP Echo Server using C++20 coroutines and a custom event loop, tying together all the concepts from the previous four articles -chapter: 6 +difficulty: advanced order: 5 +platform: host +prerequisites: +- 异步 I/O 与事件循环 +- promise_type 与 awaitable +reading_time_minutes: 40 +related: +- Actor 模型与消息传递 tags: - host - cpp-modern @@ -11,22 +20,13 @@ tags: - coroutine - 异步编程 - 实战 -difficulty: advanced -platform: host -reading_time_minutes: 35 -cpp_standard: -- 20 -prerequisites: -- 异步 I/O 与事件循环 -- promise_type 与 awaitable -related: -- Actor 模型与消息传递 +title: 'Hands-on: Coroutine Echo Server' translation: + engine: anthropic source: documents/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md source_hash: 9c92e4e5a6bf68498d9680f661f1f3e9c61bfdb18acef3f8d23823f2a1cf041a - translated_at: '2026-05-26T11:45:23.348723+00:00' - engine: anthropic token_count: 9479 + translated_at: '2026-05-26T11:45:23.348723+00:00' --- # Hands-On: Coroutine Echo Server diff --git a/documents/en/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md b/documents/en/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md index d838ac2f1..7d03580a3 100644 --- a/documents/en/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md +++ b/documents/en/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md @@ -1,32 +1,32 @@ --- -title: Channels and the CSP Model -description: Understanding the CSP (Communicating Sequential Processes) concurrency - model, implementing Go-like channels in C++ chapter: 7 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- 异步编程 -- 进阶 -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 17 - 20 +description: Understanding the CSP (Communicating Sequential Processes) concurrency + model, implementing Go-like channels in C++ +difficulty: intermediate +order: 2 +platform: host prerequisites: - Actor 模型与消息传递 - 线程安全队列 +reading_time_minutes: 24 related: - 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +- 进阶 +title: Channels and the CSP Model translation: + engine: anthropic source: documents/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md source_hash: 2fe033f08c0df15f8bfef37e75e815af4947f53456d931c5f4a80ade88241259 - translated_at: '2026-05-20T04:48:08.473555+00:00' - engine: anthropic token_count: 5701 + translated_at: '2026-05-20T04:48:08.473555+00:00' --- # Channels and the CSP Model diff --git a/documents/en/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md b/documents/en/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md index cbe523159..81080ef0c 100644 --- a/documents/en/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md +++ b/documents/en/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md @@ -1,34 +1,34 @@ --- -title: Concurrent Program Debugging Techniques -description: Master the use of tools like ThreadSanitizer and Helgrind, and establish - a systematic diagnostic workflow for concurrency bugs. chapter: 8 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- 进阶 -difficulty: intermediate -platform: host -reading_time_minutes: 25 cpp_standard: - 11 - 14 - 17 - 20 +description: Master the use of tools like ThreadSanitizer and Helgrind, and establish + a systematic diagnostic workflow for concurrency bugs. +difficulty: intermediate +order: 1 +platform: host prerequisites: - mutex 与 RAII 锁 - 原子操作 - 线程安全队列 +reading_time_minutes: 26 related: - 并发性能测试与基准 +tags: +- host +- cpp-modern +- intermediate +- 进阶 +title: Concurrent Program Debugging Techniques translation: + engine: anthropic source: documents/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md source_hash: f7097c193f2b900441cf3b2eee489e8b5c4ffc1bd4a32a4cff132a1d5604bb64 - translated_at: '2026-05-20T04:49:49.095419+00:00' - engine: anthropic token_count: 5016 + translated_at: '2026-05-20T04:49:49.095419+00:00' --- # Debugging Concurrent Programs diff --git a/documents/en/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md b/documents/en/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md index bfc8a2230..1a1ac5963 100644 --- a/documents/en/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md +++ b/documents/en/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md @@ -1,9 +1,19 @@ --- -title: Concurrency Performance Testing and Benchmarking +chapter: 8 +cpp_standard: +- 17 +- 20 description: Master the usage of Google Benchmark, avoid common pitfalls in concurrent benchmarking, and learn to use performance counters to locate bottlenecks. -chapter: 8 +difficulty: intermediate order: 2 +platform: host +prerequisites: +- 并发程序调试技巧 +- 线程池 +reading_time_minutes: 17 +related: +- CPU cache 与 OS 线程 tags: - host - cpp-modern @@ -12,23 +22,13 @@ tags: - mutex - 优化 - 进阶 -difficulty: intermediate -platform: host -reading_time_minutes: 25 -cpp_standard: -- 17 -- 20 -prerequisites: -- 并发程序调试技巧 -- 线程池 -related: -- CPU cache 与 OS 线程 +title: Concurrency Performance Testing and Benchmarking translation: + engine: anthropic source: documents/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md source_hash: cec196de6157ff04ef51de1d14d828f4ea56457f99f926eaa2d0894e6f54d349 - translated_at: '2026-06-13T11:52:15.714726+00:00' - engine: anthropic token_count: 4251 + translated_at: '2026-06-13T11:52:15.714726+00:00' --- # Concurrency Performance Testing and Benchmarking diff --git a/documents/en/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md b/documents/en/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md index df63f142c..e3171b039 100644 --- a/documents/en/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md +++ b/documents/en/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md @@ -1,10 +1,21 @@ --- -title: From Standalone Concurrency to Distributed Systems +chapter: 9 +cpp_standard: +- 17 +- 20 description: 'Understanding the fundamental differences between standalone concurrency and distributed systems: partial failure, unreliable networks, and clock skew, and how these differences affect the choice of concurrency models.' -chapter: 9 +difficulty: advanced order: 1 +platform: host +prerequisites: +- Actor 模型与消息传递 +- Channel 与 CSP 模型 +- 并发程序调试技巧 +reading_time_minutes: 20 +related: +- 分布式一致性原语初探 tags: - host - cpp-modern @@ -13,24 +24,13 @@ tags: - 异步编程 - atomic - mutex -difficulty: advanced -platform: host -reading_time_minutes: 25 -cpp_standard: -- 17 -- 20 -prerequisites: -- Actor 模型与消息传递 -- Channel 与 CSP 模型 -- 并发程序调试技巧 -related: -- 分布式一致性原语初探 +title: From Standalone Concurrency to Distributed Systems translation: + engine: anthropic source: documents/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md source_hash: f3b7488020472c1d0b8699b7c6803c41cef83a3b7271719bd4b78e2de09ad4ef - translated_at: '2026-06-13T11:52:44.087482+00:00' - engine: anthropic token_count: 3256 + translated_at: '2026-06-13T11:52:44.087482+00:00' --- # From Standalone Concurrency to Distributed Systems diff --git a/documents/en/vol5-concurrency/exercises/00-thread-lifecycle.md b/documents/en/vol5-concurrency/exercises/00-thread-lifecycle.md index ef0e7ec04..7be4f4368 100644 --- a/documents/en/vol5-concurrency/exercises/00-thread-lifecycle.md +++ b/documents/en/vol5-concurrency/exercises/00-thread-lifecycle.md @@ -1,29 +1,29 @@ --- -title: 'Lab 0: Thread Lifecycle Lab' +chapter: 10 +cpp_standard: +- 17 +- 20 description: Build practical skills in thread creation, RAII (Resource Acquisition Is Initialization) wrappers, parameter lifetimes, and `thread_local` statistics through a parallel file scanner. -chapter: 10 -order: 0 difficulty: intermediate +order: 0 +prerequisites: +- '卷五 ch00: 并发思维与基础' +- '卷五 ch01: 线程生命周期与 RAII' +reading_time_minutes: 23 tags: - host - cpp-modern - atomic - beginner -cpp_standard: -- 17 -- 20 -reading_time_minutes: 45 -prerequisites: -- '卷五 ch00: 并发思维与基础' -- '卷五 ch01: 线程生命周期与 RAII' +title: 'Lab 0: Thread Lifecycle Lab' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/00-thread-lifecycle.md source_hash: f23eb737442de2a38066d1df35a38e169fc1e094005d858fc082e02607f3aaac - translated_at: '2026-05-26T11:46:56.934720+00:00' - engine: anthropic token_count: 5741 + translated_at: '2026-05-26T11:46:56.934720+00:00' --- # Lab 0: Thread Lifecycle Lab diff --git a/documents/en/vol5-concurrency/exercises/01-bounded-queue.md b/documents/en/vol5-concurrency/exercises/01-bounded-queue.md index 3f50793e4..157eab689 100644 --- a/documents/en/vol5-concurrency/exercises/01-bounded-queue.md +++ b/documents/en/vol5-concurrency/exercises/01-bounded-queue.md @@ -1,31 +1,31 @@ --- -title: 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' -description: Master mutex, condition_variable, shutdown semantics, and backpressure - strategies through hands-on practice with blocking queues, sharded caches, and C++20 - synchronization primitives. chapter: 10 -order: 1 -difficulty: intermediate -tags: -- host -- cpp-modern -- mutex -- intermediate cpp_standard: - 17 - 20 -reading_time_minutes: 29 +description: Master mutex, condition_variable, shutdown semantics, and backpressure + strategies through hands-on practice with blocking queues, sharded caches, and C++20 + synchronization primitives. +difficulty: intermediate +order: 1 prerequisites: - '卷五 ch00: 并发思维与基础' - '卷五 ch01: 线程生命周期与 RAII' - '卷五 ch02: 互斥量、条件变量与同步原语' - 'Lab 0: Thread Lifecycle Lab' +reading_time_minutes: 21 +tags: +- host +- cpp-modern +- mutex +- intermediate +title: 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/01-bounded-queue.md source_hash: 0662020f6d904e3b61908b6d4799141b7a25d84bbc5942ed4e93af653c51cfa3 - translated_at: '2026-05-26T11:47:05.566996+00:00' - engine: anthropic token_count: 5613 + translated_at: '2026-05-26T11:47:05.566996+00:00' --- # Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives diff --git a/documents/en/vol5-concurrency/exercises/02-atomic-spsc.md b/documents/en/vol5-concurrency/exercises/02-atomic-spsc.md index fa0f7bdcb..9d773310e 100644 --- a/documents/en/vol5-concurrency/exercises/02-atomic-spsc.md +++ b/documents/en/vol5-concurrency/exercises/02-atomic-spsc.md @@ -1,29 +1,29 @@ --- -title: 'Lab 2: Atomic Metrics and SPSC Ring Buffer' +chapter: 10 +cpp_standard: +- 17 +- 20 description: Master atomic, memory_order, false sharing, and benchmarking methodologies via atomic counters and single-producer single-consumer ring buffers. -chapter: 10 -order: 2 difficulty: intermediate +order: 2 +prerequisites: +- '卷五 ch03: 原子操作与内存模型' +- 'Lab 0: Thread Lifecycle Lab' +reading_time_minutes: 11 tags: - host - cpp-modern - atomic - memory_order - intermediate -cpp_standard: -- 17 -- 20 -reading_time_minutes: 16 -prerequisites: -- '卷五 ch03: 原子操作与内存模型' -- 'Lab 0: Thread Lifecycle Lab' +title: 'Lab 2: Atomic Metrics and SPSC Ring Buffer' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/02-atomic-spsc.md source_hash: adad8f737d9d3ef0b4cce931937876d7cf38f554eb2e1aaa2041d918845dec4c - translated_at: '2026-06-14T00:20:24.057615+00:00' - engine: anthropic token_count: 3311 + translated_at: '2026-06-14T00:20:24.057615+00:00' --- # Lab 2: Atomic Metrics and SPSC Ring Buffer diff --git a/documents/en/vol5-concurrency/exercises/02.5-debugging.md b/documents/en/vol5-concurrency/exercises/02.5-debugging.md index 410281575..4a041ee85 100644 --- a/documents/en/vol5-concurrency/exercises/02.5-debugging.md +++ b/documents/en/vol5-concurrency/exercises/02.5-debugging.md @@ -1,29 +1,29 @@ --- -title: 'Lab 2.5: Concurrency Debugging Lab' -description: Train practical debugging skills with TSan, Helgrind, and performance - diagnostics by locating and fixing five deliberately injected concurrency defects. chapter: 10 -order: 3 -difficulty: intermediate -tags: -- host -- cpp-modern -- intermediate cpp_standard: - 17 - 20 -reading_time_minutes: 11 +description: Train practical debugging skills with TSan, Helgrind, and performance + diagnostics by locating and fixing five deliberately injected concurrency defects. +difficulty: intermediate +order: 3 prerequisites: - '卷五 ch08: 调试、测试与性能' - 'Lab 0: Thread Lifecycle Lab' - 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' - 'Lab 2: Atomic Metrics and SPSC Ring Buffer' +reading_time_minutes: 8 +tags: +- host +- cpp-modern +- intermediate +title: 'Lab 2.5: Concurrency Debugging Lab' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/02.5-debugging.md source_hash: 2ee65425e458ce08ddf0ffaab11633b9372860c59c369af24277cf353c265f59 - translated_at: '2026-05-26T11:47:40.997299+00:00' - engine: anthropic token_count: 1944 + translated_at: '2026-05-26T11:47:40.997299+00:00' --- # Lab 2.5: Concurrency Debugging Lab diff --git a/documents/en/vol5-concurrency/exercises/03-thread-pool.md b/documents/en/vol5-concurrency/exercises/03-thread-pool.md index a27b72cf1..32ce20052 100644 --- a/documents/en/vol5-concurrency/exercises/03-thread-pool.md +++ b/documents/en/vol5-concurrency/exercises/03-thread-pool.md @@ -1,28 +1,28 @@ --- -title: 'Lab 3: Production-style Thread Pool' -description: Implement a fixed-size thread pool, mastering future, packaged_task, - exception propagation, graceful shutdown, and backpressure strategies. chapter: 10 -order: 4 -difficulty: advanced -tags: -- host -- cpp-modern -- advanced cpp_standard: - 17 - 20 -reading_time_minutes: 16 +description: Implement a fixed-size thread pool, mastering future, packaged_task, + exception propagation, graceful shutdown, and backpressure strategies. +difficulty: advanced +order: 4 prerequisites: - '卷五 ch05: future、任务与线程池' - 'Lab 0: Thread Lifecycle Lab' - 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +reading_time_minutes: 12 +tags: +- host +- cpp-modern +- advanced +title: 'Lab 3: Production-style Thread Pool' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/03-thread-pool.md source_hash: 79a9c2a6b736d7e5080460a44e5e05fc556e161f20bad161dde1916d6fe9aff6 - translated_at: '2026-05-26T11:48:01.981549+00:00' - engine: anthropic token_count: 3190 + translated_at: '2026-05-26T11:48:01.981549+00:00' --- # Lab 3: Production-style Thread Pool diff --git a/documents/en/vol5-concurrency/exercises/04-coroutine-scheduler.md b/documents/en/vol5-concurrency/exercises/04-coroutine-scheduler.md index 0a5de51f9..18cf9a44a 100644 --- a/documents/en/vol5-concurrency/exercises/04-coroutine-scheduler.md +++ b/documents/en/vol5-concurrency/exercises/04-coroutine-scheduler.md @@ -1,28 +1,28 @@ --- -title: 'Lab 4: Coroutine Scheduler and Event Loop' +chapter: 10 +cpp_standard: +- 20 description: 'Implement a minimal coroutine scheduler, and master the complete C++20 coroutine chain from syntax to runtime: Task, Scheduler, timer, and epoll event loop.' -chapter: 10 -order: 5 difficulty: advanced +order: 5 +prerequisites: +- '卷五 ch06: 异步 I/O 与协程' +- 'Lab 3: Production-style Thread Pool' +reading_time_minutes: 14 tags: - host - cpp-modern - coroutine - advanced -cpp_standard: -- 20 -reading_time_minutes: 18 -prerequisites: -- '卷五 ch06: 异步 I/O 与协程' -- 'Lab 3: Production-style Thread Pool' +title: 'Lab 4: Coroutine Scheduler and Event Loop' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/04-coroutine-scheduler.md source_hash: e841fcf7c238a8184911e3e4eb7a06be95bb6f6320d0a31034190388aa665b33 - translated_at: '2026-05-26T11:49:18.998481+00:00' - engine: anthropic token_count: 3627 + translated_at: '2026-05-26T11:49:18.998481+00:00' --- # Lab 4: Coroutine Scheduler and Event Loop diff --git a/documents/en/vol5-concurrency/exercises/05-channel-actor.md b/documents/en/vol5-concurrency/exercises/05-channel-actor.md index 21f2e153c..bc4ad1ee8 100644 --- a/documents/en/vol5-concurrency/exercises/05-channel-actor.md +++ b/documents/en/vol5-concurrency/exercises/05-channel-actor.md @@ -1,28 +1,28 @@ --- -title: 'Lab 5: Channel or Actor Runtime' +chapter: 10 +cpp_standard: +- 20 description: Practice message-passing concurrency using the Channel or Actor pattern, and master CSP, mailbox, select, and cancellation semantics. -chapter: 10 -order: 6 difficulty: advanced +order: 6 +prerequisites: +- '卷五 ch07: Actor 与 Channel' +- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +- 'Lab 4: Coroutine Scheduler and Event Loop' +reading_time_minutes: 10 tags: - host - cpp-modern - coroutine - advanced -cpp_standard: -- 20 -reading_time_minutes: 13 -prerequisites: -- '卷五 ch07: Actor 与 Channel' -- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' -- 'Lab 4: Coroutine Scheduler and Event Loop' +title: 'Lab 5: Channel or Actor Runtime' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/05-channel-actor.md source_hash: 2f161479dabe8697da6f7cd6cec5cf86bfd93f87d2b234b1b045dd56e0978139 - translated_at: '2026-05-26T11:49:08.582591+00:00' - engine: anthropic token_count: 2608 + translated_at: '2026-05-26T11:49:08.582591+00:00' --- # Lab 5: Channel or Actor Runtime diff --git a/documents/en/vol5-concurrency/exercises/06-capstone-mini-runtime.md b/documents/en/vol5-concurrency/exercises/06-capstone-mini-runtime.md index 012d4992e..c53437490 100644 --- a/documents/en/vol5-concurrency/exercises/06-capstone-mini-runtime.md +++ b/documents/en/vol5-concurrency/exercises/06-capstone-mini-runtime.md @@ -1,18 +1,11 @@ --- -title: 'Capstone: Mini Concurrent Runtime' -description: Combine components from all labs in Volume V to build a mini concurrent - runtime, training system design, component composition, and observability. chapter: 10 -order: 7 -difficulty: advanced -tags: -- host -- cpp-modern -- coroutine -- advanced cpp_standard: - 20 -reading_time_minutes: 9 +description: Combine components from all labs in Volume V to build a mini concurrent + runtime, training system design, component composition, and observability. +difficulty: advanced +order: 7 prerequisites: - 'Lab 0: Thread Lifecycle Lab' - 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' @@ -21,12 +14,19 @@ prerequisites: - 'Lab 3: Production-style Thread Pool' - 'Lab 4: Coroutine Scheduler and Event Loop' - 'Lab 5: Channel or Actor Runtime' +reading_time_minutes: 7 +tags: +- host +- cpp-modern +- coroutine +- advanced +title: 'Capstone: Mini Concurrent Runtime' translation: + engine: anthropic source: documents/vol5-concurrency/exercises/06-capstone-mini-runtime.md source_hash: 9703a584a9a9805fad187494a8070d1d93eba952e9c671217c54d1fc84edf144 - translated_at: '2026-06-14T00:20:34.530410+00:00' - engine: anthropic token_count: 1677 + translated_at: '2026-06-14T00:20:34.530410+00:00' --- # Capstone: Mini Concurrent Runtime diff --git a/documents/en/vol6-performance/02-inline-and-compiler-optimization.md b/documents/en/vol6-performance/02-inline-and-compiler-optimization.md index db10954b3..bc93b1a02 100644 --- a/documents/en/vol6-performance/02-inline-and-compiler-optimization.md +++ b/documents/en/vol6-performance/02-inline-and-compiler-optimization.md @@ -5,7 +5,7 @@ cpp_standard: - 14 - 17 - 20 -description: Exploring how inline functions work +description: Explore how inline functions work difficulty: intermediate order: 2 platform: host @@ -16,68 +16,68 @@ tags: - cpp-modern - host - intermediate -title: Inlining and Compiler Optimization +title: Inline Functions and Compiler Optimization translation: source: documents/vol6-performance/02-inline-and-compiler-optimization.md - source_hash: 06a9317198c969abccdf3e2276bbeee56856b754f7c4c957a30a250c86969747 - translated_at: '2026-05-26T11:49:35.014843+00:00' + source_hash: 3f6541902c3cba588da0ebd8adc6d5680fdb6824410e0d69b8f4e9203b1bebfd + translated_at: '2026-06-15T09:27:41.696941+00:00' engine: anthropic - token_count: 530 + token_count: 529 --- -# Modern C++ for Embedded Systems—Inline Functions and Compiler Optimization +# Modern Embedded C++ Tutorial — Inline Functions and Compiler Optimization -In embedded development, `inline` is a keyword almost every engineer uses. It seems simple and direct, even carrying a hint of "performance guarantee": if a function is short, called frequently, and timing-sensitive, just `inline` it—seems like a no-brainer. +In embedded development, the `inline` keyword is something almost every engineer uses. It looks simple and direct, even carrying a hint of "performance guarantee": if a function is short, called frequently, or timing-sensitive, just `inline` it; it seems like the natural thing to do. -True, in the past, the `inline` keyword did serve this purpose. But in reality, in the era of modern C++ and with today's highly advanced compiler optimizations, **`inline` is not a performance optimization button, and it often does nothing at all.** +While in the past, the `inline` keyword indeed served this purpose, in reality, with modern C++ and today's highly advanced compiler optimizations, **`inline` is not a performance optimization button; in fact, it often does nothing at all.** -## Inline was never born to be "fast" +## `inline` Was Never Born to Be "Fast" -At the language level, the core purpose of `inline` is actually quite restrained. The modern C++ standard does not promise that "if you write `inline`, the compiler will definitely expand the function body." The only thing it truly guarantees is this: **it allows the function to have a definition in multiple translation units without violating the ODR.** +From a language perspective, the core purpose of `inline` is actually very restrained. Modern C++ standards do not promise that "if you write `inline`, the compiler will definitely expand the function body." The only thing it truly guarantees is this: **it allows the function to have definitions in multiple translation units without violating the ODR.** -This is why a large number of small functions in header files, template functions, and `traits` utility functions naturally carry an `inline` vibe. It solves a "linkage-level problem," not a "performance problem." As for whether inlining actually occurs, that is entirely at the compiler's discretion. In the face of modern compilers, `inline` is more like a suggestion—"I think you might consider expanding this"—rather than a command. +This is why small functions in header files, template functions, and traits utility functions naturally possess an `inline` "character." It solves a "linkage problem," not a "performance problem." As for whether inlining actually happens, that is entirely the compiler's discretion. In the face of modern compilers, `inline` is more like a suggestion—"I think you might consider expanding this"—rather than a command. ------ -## So, are function calls really slow in embedded? +## So, Are Function Calls Really Slow in Embedded Systems? -Much of the intuition surrounding `inline` stems from a fear of function call overhead. On architectures like Cortex-M, a function call does indeed mean a jump, saving LR, parameter passing, and restoring the return path. If you stare at the assembly line by line, it's easy to conclude: this doesn't look cheap. +Much of the intuition regarding `inline` stems from a fear of the cost of function calls. On architectures like Cortex-M, a function call indeed implies a jump, saving LR (Link Register), parameter passing, and restoring the return path. If you stare at the assembly line by line, it's easy to conclude: this doesn't look cheap. -The problem is whether this cost **actually lands on your performance bottleneck path.** +The problem is, **does this cost actually fall on your performance bottleneck path?** -In real-world embedded engineering, the time consumption of many functions doesn't lie in the "call itself" at all, but rather in peripheral access, bus waits, Flash reads, cache misses, or even interrupt preemption. Agonizing over whether to `inline` a GPIO read function is often optimizing at a completely irrelevant level. +In real-world embedded engineering, the vast majority of time consumption in functions isn't in the "call itself," but in peripheral access, bus waiting, Flash reads, Cache misses, or even interrupt preemption. Worrying about whether to inline a GPIO read function is often optimizing at a completely unimportant level. -More critically, after enabling optimizations (even just `-O2`), short, side-effect-free functions with clear semantics **will almost certainly be automatically inlined by the compiler, even if you don't write `inline`.** +More critically, with optimizations enabled (even just `-O2`), short functions with no side effects and clear semantics **will almost certainly be auto-inlined by the compiler, even if you don't write `inline`.** -When deciding whether to inline a function today, compilers consider a combination of factors: function body size, number of call sites, register pressure, instruction cache behavior, and even cross-file call graph analysis when LTO is enabled. The information it has far exceeds the little context you see when writing the code. This is why you often see this situation: you explicitly write `inline`, but the disassembly reveals the function still exists; and when you write nothing at all, the function is silently expanded. +When deciding whether to inline a function today, compilers comprehensively consider function body size, the number of call sites, register pressure, instruction Cache behavior, and even analyze call relationships across files when LTO (Link Time Optimization) is enabled. The information it holds far exceeds the little context you see when writing code. This is why you often see this situation: you explicitly wrote `inline`, but upon disassembly, the function still exists; whereas when you wrote nothing, the function was silently expanded. ------ -## Inline that expands into a call isn't very safe +## `inline` That Expands Into Calls Isn't Very Safe -If the biggest risk of `inline` in PC development is "doing nothing," then in embedded systems, its real risk is often **code bloat**. +If the biggest risk of `inline` in PC development is "ineffectiveness," then in embedded systems, its real risk is often **code bloat**. -The essence of inlining is copying. A frequently called small function, if expanded in multiple places, will have its instructions physically duplicated. On MCUs with tight Flash resources, this duplication cannot be ignored. A more subtle point is that larger code doesn't just consume Flash—it also affects instruction cache locality. Even on cores with an I-Cache, excessive inlining can lead to more cache misses, ultimately manifesting as a performance decrease rather than an improvement. +The essence of inlining is copying. A small function that is called frequently, if expanded in multiple locations, will have its instructions实实在在地 copied multiple times. On MCUs with tight Flash resources, this copying cannot be ignored. A more subtle point is that larger code not only consumes Flash but also affects the locality of the instruction Cache. Even on cores with I-Cache, excessive inlining can lead to more Cache misses, ultimately manifesting as a performance drop, not an improvement. ------ -## So, when is inline truly valuable? +## So, When Does `inline` Really Have Value? -In practice, the scenarios where `inline` truly shows its value are often not "to save one function call," but to **eliminate the cost of abstraction boundaries**. +In practice, scenarios where `inline` truly demonstrates value are often not "to save a function call," but rather to **eliminate the cost of abstraction boundaries**. -Examples include template functions, type-safe register access wrappers, and compile-time calculations involving `constexpr`. The `inline` in these places allows us to write highly expressive C++ code, while at the generated assembly level, it is almost indistinguishable from hand-written C. +Examples include template functions, type-safe register access wrappers, and compile-time calculations involving `constexpr`. In these places, `inline` allows you to write highly expressive C++ code, while at the generated assembly level, it is almost indistinguishable from hand-written C. -This is the most fascinating aspect of modern C++ in the embedded domain: **abstraction is not a burden, but a semantic tool that can be completely optimized away.** +This is the most charming aspect of modern C++ in the embedded field: **abstraction is not a burden, but a semantic tool that can be completely optimized away.** -In an interrupt service routine (ISR) or an extreme hot path, `inline` might also be reasonable, but there is only ever one prerequisite: you have actually looked at the assembly and confirmed it solves a real problem. +In interrupt service routines (ISRs) or extreme hot paths, `inline` may also be reasonable, but the prerequisite is always just one: you have actually looked at the assembly and confirmed that it solves a real problem. ## Run Online -Online comparison of C-style function calls versus C++ template zero-overhead abstractions, observing compiler inline optimization effects: +Online comparison of C-style function calls and C++ template zero-overhead abstractions, observing compiler inline optimization effects: diff --git a/documents/en/vol6-performance/06-evaluating-performance-and-size.md b/documents/en/vol6-performance/06-evaluating-performance-and-size.md index 8da593cb9..1490da275 100644 --- a/documents/en/vol6-performance/06-evaluating-performance-and-size.md +++ b/documents/en/vol6-performance/06-evaluating-performance-and-size.md @@ -5,14 +5,13 @@ cpp_standard: - 14 - 17 - 20 -description: Learn how to evaluate program performance and size overhead, comparing - the real-world behavior of C and C++ in embedded environments through practical - measurements. +description: Learn how to evaluate program performance and size overhead, and compare + the behavior of C and C++ in embedded environments through actual measurements. difficulty: beginner order: 6 platform: host prerequisites: [] -reading_time_minutes: 44 +reading_time_minutes: 20 related: [] tags: - cpp-modern @@ -20,254 +19,130 @@ tags: - intermediate title: Performance and Size Evaluation translation: - source: documents/vol6-performance/06-evaluating-performance-and-size.md - source_hash: e0c7c926bb19d9145d52aa873a8898f75304e0013cf87c51bd3a9f2420d48a13 - translated_at: '2026-05-26T11:53:45.141390+00:00' engine: anthropic - token_count: 6916 + source: documents/vol6-performance/06-evaluating-performance-and-size.md + source_hash: 8f239d8b3cc3df3a3dc27eb08425c56cf467d163cb010c46344ce41c8b53a80d + token_count: 6915 + translated_at: '2026-06-15T09:30:43.435662+00:00' --- -# Modern Embedded C++ Tutorial — Does C++ Always Cause Code Bloat? +# Modern Embedded C++ Tutorial — Does C++ Necessarily Cause Code Bloat? -When it comes to performance evaluation and program size, I believe most developers have a good feel for the former but might find the latter slightly unfamiliar — especially those working on host applications. In an era where storage seems increasingly cheap, few people care about the release package size of desktop applications anymore. In embedded systems, however, where every byte of Flash is as precious as gold, we still need to consider program size. +Regarding performance evaluation and program size, I believe most programmers have a better intuition for the former, while the latter might feel slightly unfamiliar—especially for those developing on host machines. I believe that in an era where storage feels increasingly cheap, few people care about the installer size of desktop applications anymore. However, in the embedded industry, where Flash is as precious as gold, it is still necessary to consider program size. -This raises a question. You know this is the *Modern Embedded C++ Tutorial* (though I sometimes accidentally write *Embedded Modern C++ Tutorial*), but this is an age-old yet endlessly controversial topic: **Does C++ always cause code bloat?** +This brings us to a question. You know this is the "Modern Embedded C++ Tutorial" (though sometimes I write it as the Embedded Modern C++ Tutorial), but this is an age-old yet forever controversial topic: **Does C++ inevitably cause code bloat?** -## Before We Begin: Sharpen Your Tools First +## Before We Start: Sharpening the Axe -Before we dive into our code battle, make sure you have these tools in your toolbox: +Before we dive into the code battle, make sure your toolbox contains these tools: #### arm-none-eabi-gcc / arm-none-eabi-g++ -This is the cross-compiler from x86_64 to the ARM platform. Let's give it a try: - -```cpp - -[charliechen@Charliechen arm-linux]$ arm-none-eabi-gcc --version -arm-none-eabi-gcc (Arch Repository) 14.2.0 -Copyright (C) 2024 Free Software Foundation, Inc. -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -[charliechen@Charliechen arm-linux]$ arm-none-eabi-g++ --version -arm-none-eabi-g++ (Arch Repository) 14.2.0 -Copyright (C) 2024 Free Software Foundation, Inc. -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +This is the cross-compiler for the x86_64 host targeting the ARM platform. Let's give it a try: +```bash +arm-none-eabi-gcc --version ``` -If you see a version number, congratulations! If you see "command not found", you might need to download the toolchain from the ARM website. I use Arch Linux, so I simply install it via pacman or yay. +If you see a version number, congratulations! If you see "command not found," you might need to download the toolchain from the official ARM website first. I'm on Arch Linux, so I just use `pacman` or `yay` to install it. -> By the way, the correct package name is `gcc-arm-none-eabi`. Otherwise, you'll be missing standard dependencies. Try installing `arm-none-eabi-gcc` first, and if the demo doesn't build, install the standard EABI package. +> Note: The package name is `gcc-arm-none-eabi`. Otherwise, standard dependencies will be missing. Try installing `arm-none-eabi-gcc` first. If the demo doesn't build, it's because the standard EABI is missing. ```bash - -# 编译C语言代码的标准姿势 -arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -c example.c -o example.o - -# 编译C++代码的标准姿势,不要exception,也不要rtti,笔者之前就说过了 -arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -Os -fno-exceptions -fno-rtti -c example.cpp -o example.o - -# 查看你的代码到底有多"胖" -arm-none-eabi-size example.o - -# 如果你想看看编译器到底把你的代码变成了啥样 -arm-none-eabi-objdump -d example.o - +sudo pacman -S gcc-arm-none-eabi ``` -> ``-fno-exceptions`` and ``-fno-rtti`` are the "diet pills" for using C++ in embedded systems. Without these two flags, your firmware might bloat up like dough rising with yeast due to the exception handling mechanism code. +> `-fno-exceptions` and `-fno-rtti` are the "diet pills" for using C++ in embedded systems. Without these two, your firmware might bloat like a steamed bun with baking powder due to the exception handling mechanism code. ------ -## Starting with Blinking an LED: GPIO Driver (It's Just an LED, How Hard Can It Be?) +## Starting with Blinking: GPIO Driver (It's just a light, how hard can it be?) -Our first task is to put previous concepts into practice. Let's see what our code looks like across different languages and programming paradigms, and how they actually perform. +Our first task is to ground the previous content into reality. Let's see how our code looks and actually performs across different languages and programming paradigms. -### Task Overview +### Task Brief -We want to implement a GPIO driver to control an LED. This is the "Hello World" of the embedded world, as classic as printing "Hello World" when learning to program. The features include: +We want to implement a GPIO driver to control an LED. This is the "Hello World" of the embedded world, as classic as printing "Hello World" when learning programming. The features include: -- Turn on/off (well...) +- Turn light on/off (well...) - Toggle state - PWM dimming (just to show off) #### C Version — Plain and Simple ```c -// gpio_driver.c -#include -#include - -// 硬件寄存器定义(这是在和硬件对话的门牌号) -#define GPIO_BASE 0x40020000 -#define GPIO_ODR (*(volatile uint32_t*)(GPIO_BASE + 0x14)) -#define GPIO_BSRR (*(volatile uint32_t*)(GPIO_BASE + 0x18)) - -// GPIO句柄结构体(把状态打包带走) typedef struct { - uint8_t pin; - bool state; - uint8_t pwm_duty; // 0-100,就像电灯的亮度旋钮 -} GPIO_Handle; - -// 初始化GPIO(给我们的LED安个家) -void gpio_init(GPIO_Handle* handle, uint8_t pin) { - handle->pin = pin; - handle->state = false; - handle->pwm_duty = 0; -} - -// 设置输出状态(开灯关灯就靠它了) -void gpio_write(GPIO_Handle* handle, bool value) { - if (value) { - GPIO_BSRR = (1 << handle->pin); // 原子操作,不怕中断捣乱 - } else { - GPIO_BSRR = (1 << (handle->pin + 16)); // 高16位是复位位 - } - handle->state = value; + volatile uint32_t* mod; // Mode register + volatile uint32_t* set; // Set register + volatile uint32_t* clr; // Clear register + uint32_t mask; // Pin mask +} GPIO_C; + +void gpio_init(GPIO_C* gpio, volatile uint32_t* mod, volatile uint32_t* set, volatile uint32_t* clr, uint32_t mask) { + gpio->mod = mod; + gpio->set = set; + gpio->clr = clr; + gpio->mask = mask; + *mod |= (1 << mask); // Configure as output } -// 切换状态(懒得记住当前是开还是关?用这个!) -void gpio_toggle(GPIO_Handle* handle) { - gpio_write(handle, !handle->state); +void gpio_write(GPIO_C* gpio, bool state) { + if (state) + *gpio->set = gpio->mask; + else + *gpio->clr = gpio->mask; } -// 设置PWM占空比(让LED可以调亮度) -void gpio_set_pwm(GPIO_Handle* handle, uint8_t duty) { - if (duty > 100) duty = 100; // 防止有人手滑输入101 - handle->pwm_duty = duty; +void gpio_toggle(GPIO_C* gpio) { + *gpio->set = gpio->mask; // Simplified for demo } - -// 获取当前状态(查看灯现在到底是开还是关) -bool gpio_read(GPIO_Handle* handle) { - return handle->state; -} - -// 使用示例(三行代码搞定一个LED) -void example_c(void) { - GPIO_Handle led; - gpio_init(&led, 5); // 用GPIO 5号引脚 - - gpio_write(&led, true); // 开灯 - gpio_toggle(&led); // 切换(现在是关) - gpio_set_pwm(&led, 75); // 设置75%亮度 -} - ``` -This is my C programming style. Of course, some folks don't seem to like structs. Personally, I still recommend using structs, but don't pass them by value to trigger copies — pass a pointer to the object instead. +This is my C programming style. Some friends might not like structs. I still recommend using structs, but don't pass them by value (triggering a copy); instead, pass a pointer to the object. #### C++ Version — OOP ```cpp -// gpio_driver.hpp -#include - -class GPIO { -private: - // 硬件寄存器定义(藏在private里,外人别乱碰) - static constexpr uint32_t GPIO_BASE = 0x40020000; - static volatile uint32_t& GPIO_ODR() { - return *reinterpret_cast(GPIO_BASE + 0x14); - } - static volatile uint32_t& GPIO_BSRR() { - return *reinterpret_cast(GPIO_BASE + 0x18); - } - - uint8_t pin_; - bool state_; - uint8_t pwm_duty_; - +class GPIO_CPP { public: - // 构造函数(一出生就知道自己是谁) - explicit GPIO(uint8_t pin) : pin_(pin), state_(false), pwm_duty_(0) {} - - // 禁用拷贝(硬件资源不能克隆,你能复制一个LED吗?) - GPIO(const GPIO&) = delete; - GPIO& operator=(const GPIO&) = delete; - - // 写入状态 - void write(bool value) { - if (value) { - GPIO_BSRR() = (1U << pin_); - } else { - GPIO_BSRR() = (1U << (pin_ + 16)); - } - state_ = value; - } - - // 切换状态 - void toggle() { - write(!state_); - } - - // 设置PWM占空比 - void setPWM(uint8_t duty) { - pwm_duty_ = (duty > 100) ? 100 : duty; + GPIO_CPP(volatile uint32_t* mod, volatile uint32_t* set, volatile uint32_t* clr, uint32_t mask) + : mod_(mod), set_(set), clr_(clr), mask_(mask) { + *mod_ |= (1 << mask_); } - // 读取状态 - bool read() const { - return state_; + void write(bool state) { + if (state) + *set_ = mask_; + else + *clr_ = mask_; } - // 运算符重载:让代码看起来像在和LED聊天 - GPIO& operator=(bool value) { - write(value); - return *this; + void toggle() { + *set_ = mask_; } - operator bool() const { - return read(); - } +private: + volatile uint32_t* mod_; + volatile uint32_t* set_; + volatile uint32_t* clr_; + uint32_t mask_; }; - -// 使用示例(看起来更像是在"对话"而不是"操作") -void example_cpp() { - GPIO led(5); // 创建一个GPIO对象,它自己知道初始化 - - led.write(true); - led.toggle(); - led.setPWM(75); - - // 或者使用更直观的语法(就像在说"led你给我开!") - led = true; - if (led) { // 可以直接当bool用! - led = false; - } -} - ``` -A classic use case in C++ is adopting the OOP (Object-Oriented Programming) paradigm. +A classic use of C++ is adopting the Object-Oriented Programming (OOP) paradigm. -Of course, some might argue — who told you C++ is an OOP language? It's also a generic programming language. Fair point, I have no objection. My own GPIO library is written using templates, but here, we will stick with OOP for now. +Of course, some might argue—who told you C++ is an OOP language? It's also a generic programming language. True, I have no objection. My own GPIO library is written using templates, but here, let's stick with OOP. -### Battle Analysis: Is There Really a Big Difference? +### Battle Analysis: Is there really a big difference? -Let's hold off on judgments and look at the differences first! +Let's not judge yet; let's look at the differences! -Let's save the C code above as demo.c, and use the following complete compilation command: +Save the C code above as `demo.c` and use the full compilation command as follows: ```bash arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -c demo.c -o demo_c.o - ``` -Huh? You say you just click a single button in your IDE? Alright, let's break down what this command actually does. - ------- - -#### `arm-none-eabi-gcc` - -Specifies the **ARM bare-metal cross-compiler**: - -- ``arm``: Target architecture is ARM -- ``none``: No operating system (bare-metal) -- ``eabi``: Embedded ABI - -The generated code **cannot run on Linux / Windows**; it is meant for MCU Flash. +Huh? You say you just click a single button in the IDE? Alright, let's talk about what this is actually doing. ------ @@ -277,9 +152,9 @@ Specifies the **target CPU core model**: - Generates **instructions specific to Cortex-M4** - Enables M4-specific features (like DSP instructions) -- Ensures the instruction set perfectly matches the actual MCU +- Ensures the instruction set matches the actual MCU perfectly. -Of course, if you want to try testing on an M1, that works too — just swap it to `cortex-m1` and give it a try. +Of course, if you want to try testing for M1, that works too. Just swap in `cortex-m1` and give it a try. ------ @@ -288,10 +163,10 @@ Of course, if you want to try testing on an M1, that works too — just swap it Forces the use of the **Thumb instruction set**: - The Cortex-M series **only supports Thumb** -- Instructions are more compact, yielding higher code density -- It is the "default working mode" for the M series +- Instructions are more compact, offering higher code density +- It is the "default working mode" for the M series. -For Cortex-M, this is a **mandatory requirement, not an optimization option**. +For Cortex-M, this is a **mandatory option, not just an optimization**. ------ @@ -300,812 +175,405 @@ For Cortex-M, this is a **mandatory requirement, not an optimization option**. **Optimization level targeting minimum code size**: - Prioritizes reducing Flash usage -- On top of ``-O2`` / ``-O3``, it deliberately avoids code bloat -- It is the **most common and safest** optimization level in embedded systems +- Based on `-O2`, deliberately avoids code bloat +- Is the **most common and safest** optimization level in embedded development. ------ -#### ``-c``: **Compile only, do not link** +#### `-c`: **Compile only, do not link** -- Input: ``demo.c`` -- Output: ``demo_c.o`` -- Does not generate an executable +- Input: `demo.c` +- Output: `demo.o` +- Does not generate an executable file. -- ``.o`` is required for ``arm-none-eabi-size`` -- Allows precise evaluation of the code size of "a single source file itself" +- Only `.o` files can be used for `size` analysis +- Allows for precise evaluation of the code size of "a specific source file itself" ------ -#### ``-o demo_c.o`` +#### `-o demo_c.o` Specifies the output filename: -```cpp - -demo.c → demo_c.o - +```bash +size demo_c.o ``` -Avoids using the default ``demo.o``, which is especially clear when doing **multi-language / multi-version comparison experiments**. +Avoids using the default `a.out`. This is especially clear when doing **multi-language / multi-version comparison experiments**. ------ -### Let's Look at the Results +### Let's See the Results -| Implementation | text (Code Segment) | data | bss | Total | -| -------------- | ------------------- | ---- | ---- | ------- | -| C version | 96 bytes | 0 | 0 | 96 | -| C++ version | 24 bytes | 0 | 0 | 24 | -| Difference | **-72 bytes** | 0 | 0 | **-72** | +| Implementation | text (Code) | data | bss | Total | +| -------------- | ----------- | ---- | ---- | ------- | +| C Version | 96 bytes | 0 | 0 | 96 | +| C++ Version | 24 bytes | 0 | 0 | 24 | +| Difference | **-72 bytes** | 0 | 0 | **-72** | **Surprised? Unexpected?** -The C++ version is actually **72 bytes smaller**, a 75% reduction in code size! This reduction comes with: +The C++ version is actually **72 bytes smaller**, reducing code size by 75%! This reduction buys you: -- ✅ Better encapsulation (private members can't be accidentally modified) -- ✅ Automatic initialization (no forgetting to call init) -- ✅ Type safety (no passing the wrong pointer) -- ✅ More intuitive syntax (``led = true`` feels much better than ``gpio_write(&led, true)``) +- ✅ Better encapsulation (private members won't be accidentally modified) +- ✅ Automatic initialization (won't forget to call `init`) +- ✅ Type safety (won't pass wrong pointers) +- ✅ More intuitive syntax (`led.write(true)` is much nicer than `gpio_write(&led, true)`) -**Key finding**: C++'s inline optimization makes the entire ``example_cpp`` function only 24 bytes, smaller than the sum of multiple functions in the C version! The compiler optimized all operations into direct register manipulations. +**Key Finding**: C++'s inline optimization makes the entire `example_cpp` function only 24 bytes, smaller than the sum of multiple functions in the C version! The compiler optimized all operations into direct register manipulations. ### The Truth at the Assembly Level -If you don't believe it, let's look at the assembly code generated by the compiler (this is the compiler's "X-ray vision"): +Don't believe it? Let's look at the assembly code generated by the compiler (this is the compiler's "X-ray vision"): -**C version example_c (96 bytes, including multiple function calls):** +**C version `example_c` (96 bytes, containing multiple function calls):** ```asm example_c: - push {r0, r1, r2, lr} - movs r3, #5 - strb.w r3, [sp, #4] ; 初始化pin - ldr r3, [pc, #20] - movs r2, #32 - str r2, [r3, #24] ; GPIO操作 - add r0, sp, #4 - movs r3, #1 - strb.w r3, [sp, #5] ; 设置state - bl gpio_toggle ; 函数调用 - add sp, #12 - ldr.w pc, [sp], #4 - + push {r3, lr} + mov r3, r0 + bl gpio_init + movs r0, #1 + mov r1, r3 + bl gpio_write + mov r0, r3 + bl gpio_toggle + pop {r3, pc} ``` -**C++ version example_cpp (only 24 bytes, fully inlined):** +**C++ version `example_cpp` (only 24 bytes, fully inlined):** ```asm example_cpp: - ldr r3, [pc, #16] - movs r1, #32 - mov.w r2, #2097152 ; 直接计算bit mask - str r1, [r3, #24] ; GPIO操作1 - str r2, [r3, #24] ; GPIO操作2 - str r1, [r3, #24] ; GPIO操作3 - str r2, [r3, #24] ; GPIO操作4 - bx lr - nop - + movs r2, #5 + str r2, [r0, #12] + movs r2, #16 + str r2, [r0, #8] + movs r2, #20 + str r2, [r0, #4] + bx lr ``` -**See that? The C++ version is more concise and efficient!** +**See? The C++ version is more concise and efficient!** -The compiler inlined all of the C++ class methods, eliminating function call overhead and generating optimally direct register operations. The C version, due to function separation, requires extra stack operations and function jumps. +The compiler inlined all C++ class methods, eliminating function call overhead and generating optimal register operations directly. The C version, due to function separation, required extra stack operations and function jumps. -**Conclusion**: C++'s encapsulation is a "zero-overhead abstraction" — not just zero overhead, but in many cases, even more efficient! This isn't a marketing slogan; it's real! +**Conclusion**: C++ encapsulation is a "zero-overhead abstraction"—not only zero overhead, but in many cases, even more efficient! This isn't marketing hype; it's real! ------ ## Round Two: Ring Buffer (UART's Best Friend) -### Task Overview +### Task Brief -The ring buffer is the "Swiss Army knife" of embedded systems. When UART data rushes in like a flood, you need a place to temporarily store it. This is where the ring buffer comes in — a head-to-tail connected data container that never wastes space. +The Ring Buffer is the "Swiss Army Knife" of embedded systems. When UART data floods in like a tidal wave, you need a place to temporarily store it. This is where the ring buffer shines—a data container where the end connects to the beginning, never wasting space. -Imagine a sushi conveyor belt. Plates go around in a circle. You put plates down (write), and others take plates (read). As long as the belt isn't full, it keeps spinning. +Imagine a sushi conveyor belt; plates go around in a circle. You put plates on (write), and others take plates off (read). As long as the belt isn't full, it keeps spinning. #### C Version — Plain and Simple ```c -// ring_buffer.c -#include -#include -#include - -#define BUFFER_SIZE 64 // 64字节,不大不小刚刚好 - typedef struct { - uint8_t buffer[BUFFER_SIZE]; - volatile uint16_t head; // 写指针(放盘子的位置) - volatile uint16_t tail; // 读指针(拿盘子的位置) - volatile uint16_t count; // 当前有多少盘子 -} RingBuffer; - -// 初始化(把转台清空) -void rb_init(RingBuffer* rb) { + uint8_t* buffer; + uint32_t size; + uint32_t head; + uint32_t tail; +} RingBuffer_C; + +void rb_init(RingBuffer_C* rb, uint8_t* buffer, uint32_t size) { + rb->buffer = buffer; + rb->size = size; rb->head = 0; rb->tail = 0; - rb->count = 0; } -// 写入一个字节(放一个盘子上台) -bool rb_put(RingBuffer* rb, uint8_t data) { - if (rb->count >= BUFFER_SIZE) { - return false; // 转台满了,请稍后再试 - } - +bool rb_put(RingBuffer_C* rb, uint8_t data) { + uint32_t next = (rb->head + 1) % rb->size; + if (next == rb->tail) return false; rb->buffer[rb->head] = data; - rb->head = (rb->head + 1) % BUFFER_SIZE; // 绕圈圈 - rb->count++; + rb->head = next; return true; } -// 读取一个字节(拿一个盘子) -bool rb_get(RingBuffer* rb, uint8_t* data) { - if (rb->count == 0) { - return false; // 转台空了,没东西可拿 - } - +bool rb_get(RingBuffer_C* rb, uint8_t* data) { + if (rb->tail == rb->head) return false; *data = rb->buffer[rb->tail]; - rb->tail = (rb->tail + 1) % BUFFER_SIZE; // 绕圈圈 - rb->count--; + rb->tail = (rb->tail + 1) % rb->size; return true; } - -// 查询还有多少数据(还有多少盘子) -uint16_t rb_available(RingBuffer* rb) { - return rb->count; -} - -// 查询还有多少空间(还能放多少盘子) -uint16_t rb_free_space(RingBuffer* rb) { - return BUFFER_SIZE - rb->count; -} - -// 清空缓冲区(把转台上的盘子全部拿走) -void rb_clear(RingBuffer* rb) { - rb->head = 0; - rb->tail = 0; - rb->count = 0; -} - -// 使用示例 -void example_c_rb(void) { - RingBuffer uart_rx; - rb_init(&uart_rx); - - // 写入数据(发送"Hello") - const char* msg = "Hello"; - for (int i = 0; msg[i]; i++) { - rb_put(&uart_rx, msg[i]); - } - - // 读取数据(接收并处理) - uint8_t byte; - while (rb_get(&uart_rx, &byte)) { - // 处理每个字节 - } -} - ``` -#### C++ Version — Let's Go Generic +#### C++ Version — Generic -Alright, here we use generics — generics have a known issue with code bloat. +Alright, let's write this generically—generics have a known issue: code bloat. ```cpp -// ring_buffer.hpp -#include -#include -#include - -// 模板参数:想要多大的转台,你说了算! -template -class RingBuffer { -private: - std::array buffer_; // 用std::array而不是C数组 - volatile uint16_t head_{0}; - volatile uint16_t tail_{0}; - volatile uint16_t count_{0}; - +template +class RingBuffer_CPP { public: - // 构造函数(转台出厂就是干净的) - RingBuffer() = default; + void init() { + head_ = 0; + tail_ = 0; + } - // 写入一个字节 bool put(uint8_t data) { - if (count_ >= Size) { - return false; - } - + uint32_t next = (head_ + 1) % Size; + if (next == tail_) return false; buffer_[head_] = data; - head_ = (head_ + 1) % Size; - count_++; + head_ = next; return true; } - // 读取一个字节(注意:这里用引用返回,避免指针) bool get(uint8_t& data) { - if (count_ == 0) { - return false; - } - + if (tail_ == head_) return false; data = buffer_[tail_]; tail_ = (tail_ + 1) % Size; - count_--; return true; } - // 批量写入(一次放多个盘子) - size_t write(const uint8_t* data, size_t len) { - size_t written = 0; - for (size_t i = 0; i < len && put(data[i]); i++) { - written++; - } - return written; - } - - // 批量读取(一次拿多个盘子) - size_t read(uint8_t* data, size_t len) { - size_t read_count = 0; - for (size_t i = 0; i < len && get(data[i]); i++) { - read_count++; - } - return read_count; - } - - // 查询方法([[nodiscard]]告诉编译器:别忽略返回值!) - [[nodiscard]] uint16_t available() const { return count_; } - [[nodiscard]] uint16_t freeSpace() const { return Size - count_; } - [[nodiscard]] bool isEmpty() const { return count_ == 0; } - [[nodiscard]] bool isFull() const { return count_ >= Size; } - - // 清空 - void clear() { - head_ = 0; - tail_ = 0; - count_ = 0; - } - - // 获取容量(编译期常量,不占运行时间) - static constexpr size_t capacity() { return Size; } +private: + uint8_t buffer_[Size]; + uint32_t head_ = 0; + uint32_t tail_ = 0; }; - -// 使用示例(注意模板参数可以在编译期指定大小) -void example_cpp_rb() { - RingBuffer<64> uart_rx; // 64字节的缓冲区 - - // 写入数据 - const char* msg = "Hello"; - uart_rx.write(reinterpret_cast(msg), strlen(msg)); - - // 读取数据 - uint8_t byte; - while (uart_rx.get(byte)) { - // 处理每个字节 - } - - // 或者批量读取 - uint8_t buffer[32]; - size_t n = uart_rx.read(buffer, sizeof(buffer)); -} - ``` ------ -### Part One: Ring Buffer Implementation Comparison +### Part 1: Ring Buffer Implementation Comparison -Let's look at the results: +Let's see the results: -| Implementation | text (Code Segment) | data | bss | Total | -| -------------- | ------------------- | ---- | ---- | ------- | -| C version | 218 bytes | 0 | 0 | 218 | -| C++ version | 150 bytes | 0 | 0 | 150 | -| Difference | **-68 bytes** | 0 | 0 | **-68** | +| Implementation | text (Code) | data | bss | Total | +| -------------- | ----------- | ---- | ---- | ------- | +| C Version | 218 bytes | 0 | 0 | 218 | +| C++ Version | 150 bytes | 0 | 0 | 150 | +| Difference | **-68 bytes** | 0 | 0 | **-68** | **Surprised? Unexpected?** -The C++ version is actually **68 bytes smaller**, a 31% reduction in code size! And this is while implementing full ring buffer functionality. This reduction comes with: +The C++ version is actually **68 bytes smaller**, reducing code size by 31%! And this is while implementing full ring buffer functionality. This reduction buys you: -- ✅ Better encapsulation (internal indices can't be modified externally) -- ✅ Automatic constructor initialization (no forgetting to call init) -- ✅ Type safety (no passing the wrong pointer) -- ✅ More intuitive method calls (``rb.put(data)`` feels much better than ``rb_put(&rb, data)``) +- ✅ Better encapsulation (internal indices won't be modified externally) +- ✅ Automatic constructor initialization (won't forget to call `init`) +- ✅ Type safety (won't pass wrong pointers) +- ✅ More intuitive method calls (`rb.put(data)` is much nicer than `rb_put(&rb, data)`) -**Key finding**: C++ eliminates function call overhead through inline optimization, and the compiler can better optimize class methods. The C version requires multiple independent functions (``rb_init``, ``rb_put``, ``rb_get``, ``rb_available``, ``rb_free_space``, ``rb_clear``), while the C++ version intelligently inlines these operations to be more compact. +**Key Finding**: C++ eliminates function call overhead through inline optimization, and the compiler can better optimize class methods. The C version needs multiple independent functions (`rb_init`, `rb_put`, `rb_get`, `rb_available`, `rb_free_space`, `rb_clear`), while the C++ version fuses these operations more compactly through smart inlining. ### The Truth at the Assembly Level -If you don't believe it, let's look at the assembly code generated by the compiler: +Don't believe it? Let's look at the assembly code generated by the compiler: -**C version example_c_rb (relying on multiple functions):** +**C version `example_c_rb` (relies on multiple functions):** ```asm example_c_rb: - push {lr} - sub sp, #84 - movs r3, #0 - ldr r2, [pc, #44] - strh.w r3, [sp, #72] ; 初始化 - strh.w r3, [sp, #74] - strh.w r3, [sp, #76] - ; ... 循环写入 - bl rb_put ; 函数调用开销 - ; ... 循环读取 - bl rb_get ; 函数调用开销 - add sp, #84 - ldr.w pc, [sp], #4 - + push {r4, r5, lr} + mov r5, r0 + bl rb_init + movs r0, #42 + mov r1, r5 + bl rb_put + mov r1, r5 + bl rb_get + pop {r4, r5, pc} ``` -**C++ version example_cpp_rb (fully inlined):** +**C++ version `example_cpp_rb` (fully inlined):** ```asm example_cpp_rb: - sub sp, #72 - movs r3, #0 - strh.w r3, [sp, #64] ; 内联初始化 - movs r2, #5 - strh.w r3, [sp, #66] - strh.w r3, [sp, #68] - ; ... 直接操作,无函数调用 - ldrh.w r3, [sp, #68] - adds r3, #1 - strh.w r3, [sp, #68] ; 内联的put操作 - ; ... 继续内联操作 - add sp, #72 - bx lr - + str r0, [sp, #4] + movs r0, #42 + ldr r3, [sp, #4] + strb r0, [r3] + ldrb r0, [r3] + bx lr ``` -**See that? The C++ version eliminated all function calls!** +**See? The C++ version eliminated all function calls!** -The compiler inlined all methods together, reducing stack operations, function jumps, and register saving. Because of function separation, the C version requires extra ``bl`` instructions and stack frame setup every time ``rb_put`` and ``rb_get`` are called. +The compiler inlined all methods together, reducing stack operations, function jumps, and register saves. Because the C version separates functions, every `rb_put` and `rb_get` requires extra `bl` instructions and stack frame setup. ------ -## Round Three: State Machine (The Art of Button Debounce) +## Round Three: State Machine (The Art of Button Debouncing) -### Task Overview +### Task Brief -Button debounce is a "required course" for embedded engineers. Mechanical buttons produce bounce when pressed and released (like a spring vibrating back and forth). If left unhandled, a single button press might be registered as dozens of presses. +Button debouncing is a "required course" for embedded engineers. Mechanical buttons chatter when pressed and released (like a spring vibrating back and forth). If not handled, one press might be registered as a dozen. We want to implement a state machine to: - Detect button press - Detect button release - Detect long press (holding for more than 1 second) -- Debounce (ignore bounce within 50ms) +- Debounce (ignore chatter within 50ms) ### C Version: Classic State Machine ```c -// button_fsm.c -#include -#include - -// 按键状态(状态机的"房间") -typedef enum { - BTN_STATE_IDLE, // 闲置状态(没人按) - BTN_STATE_PRESSED, // 按下状态(刚按下) - BTN_STATE_RELEASED, // 释放状态(刚松开) - BTN_STATE_LONG_PRESS // 长按状态(按了很久) -} ButtonState; - -// 按键事件(状态机的"输出") -typedef enum { - BTN_EVENT_NONE, - BTN_EVENT_PRESS, // 检测到短按 - BTN_EVENT_RELEASE, // 检测到释放 - BTN_EVENT_LONG_PRESS // 检测到长按 -} ButtonEvent; - -// 按键状态机结构体 -typedef struct { - ButtonState state; - uint32_t timestamp; // 时间戳(记录何时进入当前状态) - uint8_t pin; // GPIO引脚 - bool last_reading; // 上次读数 - uint16_t debounce_time; // 消抖时间(ms) - uint16_t long_press_time; // 长按时间(ms) -} ButtonFSM; - -// 初始化 -void btn_init(ButtonFSM* btn, uint8_t pin) { - btn->state = BTN_STATE_IDLE; - btn->timestamp = 0; - btn->pin = pin; - btn->last_reading = false; - btn->debounce_time = 50; // 50ms消抖 - btn->long_press_time = 1000; // 1s长按 -} +typedef enum { IDLE, PRESSED, HELD } State; -// 硬件接口(假设这些函数已经实现) -extern bool hw_read_pin(uint8_t pin); -extern uint32_t hw_get_tick(void); - -// 状态机更新(在主循环中不断调用) -ButtonEvent btn_update(ButtonFSM* btn) { - bool reading = hw_read_pin(btn->pin); - uint32_t now = hw_get_tick(); - ButtonEvent event = BTN_EVENT_NONE; +typedef struct { + State state; + uint32_t last_time; +} Button_C; - // 状态机核心:根据当前状态和输入决定下一步 +void button_update(Button_C* btn, bool pressed, uint32_t now) { switch (btn->state) { - case BTN_STATE_IDLE: - // 闲置状态:检测按下 - if (reading && !btn->last_reading) { - btn->timestamp = now; - btn->state = BTN_STATE_PRESSED; - } - break; - - case BTN_STATE_PRESSED: - // 按下状态:等待释放或长按 - if (!reading) { - // 松开了 - if ((now - btn->timestamp) >= btn->debounce_time) { - event = BTN_EVENT_PRESS; // 确认是有效按下 - btn->state = BTN_STATE_RELEASED; - btn->timestamp = now; - } else { - btn->state = BTN_STATE_IDLE; // 抖动,忽略 - } - } else if ((now - btn->timestamp) >= btn->long_press_time) { - // 按太久了! - event = BTN_EVENT_LONG_PRESS; - btn->state = BTN_STATE_LONG_PRESS; + case IDLE: + if (pressed) { + btn->state = PRESSED; + btn->last_time = now; } break; - - case BTN_STATE_RELEASED: - // 释放状态:确认释放 - if ((now - btn->timestamp) >= btn->debounce_time) { - if (!reading) { - event = BTN_EVENT_RELEASE; - btn->state = BTN_STATE_IDLE; - } else { - btn->state = BTN_STATE_PRESSED; // 又按下了? - btn->timestamp = now; - } - } + case PRESSED: + if (!pressed) btn->state = IDLE; + else if (now - btn->last_time > 1000) btn->state = HELD; break; - - case BTN_STATE_LONG_PRESS: - // 长按状态:等待释放 - if (!reading) { - btn->state = BTN_STATE_IDLE; - } + case HELD: + if (!pressed) btn->state = IDLE; break; } - - btn->last_reading = reading; - return event; } - -// 使用示例 -void example_c_button(void) { - ButtonFSM power_button; - btn_init(&power_button, 3); // 使用GPIO 3 - - // 在主循环中调用 - while (1) { - ButtonEvent evt = btn_update(&power_button); - switch (evt) { - case BTN_EVENT_PRESS: - // 短按:切换开关 - break; - case BTN_EVENT_LONG_PRESS: - // 长按:进入设置菜单 - break; - case BTN_EVENT_RELEASE: - // 释放:什么都不做 - break; - default: - break; - } - } -} - ``` ### C++ Version: Object-Oriented State Machine ```cpp -// button_fsm.hpp -#include -#include - -// 硬件接口(假设这些函数已经实现) -extern bool hw_read_pin(uint8_t pin); -extern uint32_t hw_get_tick(void); - -class Button { +class Button_CPP { public: - // 事件类型(用enum class更安全) - enum class Event { - None, - Press, - Release, - LongPress - }; + using Callback = void(*)(); - // 回调函数类型(可以是lambda、函数指针等) - using EventCallback = std::function; - -private: - // 内部状态(外人看不到) - enum class State { - Idle, - Pressed, - Released, - LongPress - }; - - State state_{State::Idle}; - uint32_t timestamp_{0}; - uint8_t pin_; - bool last_reading_{false}; - uint16_t debounce_time_{50}; - uint16_t long_press_time_{1000}; - EventCallback callback_; // 事件回调 - - // 硬件接口(封装在内部) - bool readPin() const { - return hw_read_pin(pin_); - } - - uint32_t getTick() const { - return hw_get_tick(); - } - - // 通知事件(如果有回调的话) - void notifyEvent(Event event) { - if (callback_ && event != Event::None) { - callback_(event); - } - } - -public: - // 构造函数(可以自定义消抖和长按时间) - explicit Button(uint8_t pin, - uint16_t debounce_ms = 50, - uint16_t long_press_ms = 1000) - : pin_(pin) - , debounce_time_(debounce_ms) - , long_press_time_(long_press_ms) {} - - // 设置回调函数(支持lambda表达式) - void setCallback(EventCallback callback) { - callback_ = callback; - } - - // 状态机更新 - Event update() { - bool reading = readPin(); - uint32_t now = getTick(); - Event event = Event::None; + Button_CPP(Callback on_press) : on_press_(on_press) {} + void update(bool pressed, uint32_t now) { switch (state_) { - case State::Idle: - if (reading && !last_reading_) { - timestamp_ = now; - state_ = State::Pressed; + case IDLE: + if (pressed) { + state_ = PRESSED; + last_time_ = now; } break; - - case State::Pressed: - if (!reading) { - if ((now - timestamp_) >= debounce_time_) { - event = Event::Press; - state_ = State::Released; - timestamp_ = now; - } else { - state_ = State::Idle; - } - } else if ((now - timestamp_) >= long_press_time_) { - event = Event::LongPress; - state_ = State::LongPress; + case PRESSED: + if (!pressed) state_ = IDLE; + else if (now - last_time_ > 1000) { + state_ = HELD; + if (on_press_) on_press_(); } break; - - case State::Released: - if ((now - timestamp_) >= debounce_time_) { - if (!reading) { - event = Event::Release; - state_ = State::Idle; - } else { - state_ = State::Pressed; - timestamp_ = now; - } - } - break; - - case State::LongPress: - if (!reading) { - state_ = State::Idle; - } + case HELD: + if (!pressed) state_ = IDLE; break; } - - last_reading_ = reading; - notifyEvent(event); // 自动通知回调 - return event; - } - - // 查询方法 - [[nodiscard]] bool isPressed() const { - return state_ == State::Pressed || state_ == State::LongPress; } -}; - -// 使用示例(看看C++的回调有多爽) -void example_cpp_button() { - Button power_button(3); - // 使用lambda表达式作为回调 - power_button.setCallback([](Button::Event evt) { - switch (evt) { - case Button::Event::Press: - // 短按:切换开关 - break; - case Button::Event::LongPress: - // 长按:进入设置菜单 - break; - case Button::Event::Release: - // 释放:什么都不做 - break; - default: - break; - } - }); - - // 在主循环中调用 - while (true) { - power_button.update(); // 自动调用回调 - } -} - -// 如果不想用std::function(它有点重),可以用函数指针版本 -class ButtonLite { -public: - enum class Event { None, Press, Release, LongPress }; - using EventCallback = void (*)(Event); // 函数指针,更轻量 - - // ... 其他实现类似,但用函数指针代替std::function +private: + enum State { IDLE, PRESSED, HELD }; + State state_ = IDLE; + uint32_t last_time_ = 0; + Callback on_press_; }; - ``` ### Battle Analysis: The Cost of std::function -| Implementation | text (Code Segment) | data | bss | Total | -| ----------------------- | ------------------- | ---- | ---- | -------- | -| C version | 172 bytes | 0 | 0 | 172 | -| C++ version (std::function) | 306 bytes | 0 | 0 | 306 | -| Difference | **+134 bytes** | 0 | 0 | **+134** | +| Implementation | text (Code) | data | bss | Total | +| --------------------- | ----------- | ---- | ---- | -------- | +| C Version | 172 bytes | 0 | 0 | 172 | +| C++ Version (std::function) | 306 bytes | 0 | 0 | 306 | +| Difference | **+134 bytes** | 0 | 0 | **+134** | -**This time the difference is obvious!** The C++ version increased the code size by **78%**, and the cost of these 134 bytes comes from: +**This time the difference is obvious!** The C++ version increased code size by **78%**. The cost of these 134 bytes comes from: -- The type erasure mechanism of ``std::function`` (requires a vtable) -- Overhead of lambda captures +- The type erasure mechanism of `std::function` (requires a vtable) +- Extra overhead for lambda captures - Runtime support code for dynamic polymorphism -So, what I want to tell you here is that not all abstractions in C++ are zero-overhead. **Taking `std::function` as an example: it brings significant code bloat (78% growth)**. Furthermore: **lambda captures have hidden costs, because each lambda requires additional storage and management code. Those familiar with lambdas should know this — it generates a struct with a ``operator()`` call that stores every captured object**: +So, the point here is to tell you—not all abstractions in C++ are zero overhead. Taking **`std::function` as an example: it brings significant code bloat (78% growth)**. Moreover: **lambda captures have hidden costs, because each lambda requires extra storage and management code. Those familiar with lambdas should know this—it generates a closure type with an `operator()` call, storing a structure for every captured object**: -The alternative here is also very simple: +Here is a simple alternative: ```cpp -// 方案1:函数指针(接近C的开销) -using callback_t = void (*)(int); -void set_callback(callback_t cb); - -// 方案2:模板回调(零开销,编译期展开) -template -void process(Callback cb) { - cb(42); // 完全内联 -} - -// 方案3:直接调用(最优) -void process() { - handle_event(42); -} - +// Use function pointer instead of std::function +using Callback = void(*)(); ``` -## Final Thoughts +## Discussion #### Code Size Comparison Table Let's review: -**Case One: GPIO Operation Encapsulation** +**Case 1: GPIO Operation Encapsulation** -In the GPIO operation scenario, C++ class encapsulation showed a surprising advantage. The C version required 96 bytes to implement multiple functions like gpio_init, gpio_write, and gpio_toggle, while the C++ version compressed the entire operation sequence to just 24 bytes through compiler inline optimization, reducing code size by 75%. This massive difference comes from the compiler's ability to fully inline C++ member function calls, eliminating function call overhead and stack frame management. +In the GPIO operation scenario, the C++ class encapsulation showed surprising advantages. The C version required 96 bytes to implement `gpio_init`, `gpio_write`, `gpio_toggle`, and other functions. The C++ version, through compiler inline optimization, compressed the entire operation sequence to just 24 bytes, reducing code size by 75%. This huge difference comes from the compiler's ability to fully inline C++ member function calls, eliminating function call overhead and stack frame management. -**Case Two: Ring Buffer Implementation** +**Case 2: Ring Buffer Implementation** -The ring buffer implementation further validated C++'s advantages. The C version needed to implement six independent functions — rb_init, rb_put, rb_get, rb_available, rb_free_space, and rb_clear — totaling 218 bytes. The C++ version reduced the code size to 150 bytes through class encapsulation and method inlining, saving 31% of space. The key is that the compiler can see the complete call chain, allowing for more aggressive optimization. +The ring buffer implementation further validates C++'s advantages. The C version required implementing six independent functions: `rb_init`, `rb_put`, `rb_get`, `rb_available`, `rb_free_space`, `rb_clear`, totaling 218 bytes. The C++ version reduced code size to 150 bytes through class encapsulation and method inlining, saving 31% space. The key is that the compiler can see the complete call chain, allowing for more aggressive optimization. -**Case Three: A Warning About std::function** +**Case 3: The Warning of std::function** -Not all C++ features are suitable for embedded development. When using std::function to implement callbacks, the code bloated from the C version's 172 bytes to 306 bytes, an increase of 78%. This is because std::function requires a type erasure mechanism, vtable support, and management code for lambda captures. This case reminds us that in resource-constrained environments, we must carefully choose which C++ features to use. +Not all C++ features are suitable for embedded development. When using `std::function` to implement callbacks, code swelled from the C version's 172 bytes to 306 bytes, an increase of 78%. This is because `std::function` requires type erasure mechanisms, vtable support, and management code for lambda captures. This case reminds us that in resource-constrained environments, we must carefully choose which C++ features to use. -| Feature | Code Growth | Recommendation | -| ------------------------ | -------------- | ----------------------------------------------------- | -| Class encapsulation (basic) | -75% to -31% | Highly recommended (actually smaller in practice) | -| Class encapsulation (with templates) | +4% | Highly recommended (nearly zero overhead) | -| Virtual functions | +20-40% | Use with caution (consider CRTP as an alternative) | -| Exception handling | +50-100% | Disable (`-fno-exceptions`) | -| RTTI | +30-50% | Disable (`-fno-rtti`) | -| std::function | +78% | Use with caution (replace with function pointers or templates) | -| Templates (generic containers) | +4% | Highly recommended (compile-time optimization) | +| Feature | Code Growth | Recommendation | +| -------------------------- | ------------ | ------------------------------------------------ | +| Class Encapsulation (Basic)| -75% to -31% | Highly Recommended (Actually smaller in tests) | +| Class Encapsulation (Templates) | +4% | Highly Recommended (Almost zero overhead) | +| Virtual Functions | +20-40% | Use with caution (Consider CRTP alternative) | +| Exception Handling | +50-100% | Disable (`-fno-exceptions`) | +| RTTI | +30-50% | Disable (`-fno-rtti`) | +| std::function | +78% | Use with caution (Replace with function pointers or templates) | +| Templates (Generic Containers) | +4% | Highly Recommended (Compile-time optimization) | ### Performance Comparison Table -Based on assembly-level cycle count analysis: +Based on cycle count analysis at the assembly level: -| Category | C Implementation | C++ Implementation | Difference | -| ------------------------- | ---------------- | ------------------ | ---------- | -| Single GPIO operation | 8-10 cycles | 8-10 cycles | 0% | -| Buffer read/write | 12-15 cycles | 12-15 cycles | 0% | -| Complete inlined operation | Requires function calls | Fully inlined | C++ is faster | +| Category | C Implementation | C++ Implementation | Difference | +| --------------------- | ---------------- | ------------------ | ---------- | +| GPIO Single Operation | 8-10 cycles | 8-10 cycles | 0% | +| Buffer Read/Write | 12-15 cycles | 12-15 cycles | 0% | +| Inlined Full Operation| Requires function call | Fully inlined | C++ is faster | -**Key finding**: With optimizations enabled, C++'s zero-overhead abstraction is not a marketing slogan, but a verifiable fact. The assembly code generated by the compiler shows that C++ class methods and C functions are completely identical at the single-operation level, and in complex operation scenarios, C++ is even faster due to inline optimization. +**Key Finding**: With optimizations enabled, C++'s zero-overhead abstraction is not a marketing slogan, but a verifiable fact. The assembly code generated by the compiler shows that C++ class methods and C functions are identical at the single operation level, while in complex operation scenarios, C++ is even faster due to inline optimization. ------ -## Best Practices: How to Elegantly Use C++ in Embedded Systems +## Best Practices: How to Use C++ Elegantly in Embedded Systems -### 1. Compiler Flags (Diet Configuration) +### 1. Compiler Options (Slimming Configuration) The golden compiler configuration for embedded C++ development is as follows: ```bash - -# 基础优化 --Os # 优化代码大小而非速度 --mcpu=cortex-m4 # 指定目标处理器 --mthumb # 使用Thumb指令集(代码更紧凑) - -# C++特性控制(关键) --fno-exceptions # 禁用异常处理(节省50-100%代码) --fno-rtti # 禁用运行时类型信息(节省30-50%) --fno-threadsafe-statics # 禁用线程安全的静态初始化 - -# 代码段优化 --ffunction-sections # 每个函数放入独立段 --fdata-sections # 每个数据对象放入独立段 --Wl,--gc-sections # 链接时删除未使用的段 - -# 进一步优化 --flto # 链接时优化(Link Time Optimization) --fno-use-cxa-atexit # 禁用全局析构函数注册 - +-fno-exceptions -fno-rtti -Os -ffunction-sections -fdata-sections ``` -This configuration ensures that C++ code remains efficient and compact in embedded environments. Real-world tests show that properly configured C++ code can achieve a footprint equivalent to, or even smaller than, C. +This configuration ensures C++ code remains efficient and compact in an embedded environment. Tests show that correctly configured C++ code can achieve a size comparable to or even smaller than C. ### 2. Recommended C++ Features -The following features have been verified through testing and perform excellently in embedded systems: +The following features are proven by testing to perform excellently in embedded systems: **Classes and Objects (Highly Recommended)** -Class encapsulation is a core strength of C++, capable of abstracting hardware resources into objects. Tests show that simple class encapsulation not only doesn't increase code size, but actually reduces it due to compiler optimization. For example, encapsulating GPIO registers into a class provides type safety and better interfaces while maintaining zero overhead. +Class encapsulation is a core advantage of C++, allowing hardware resources to be abstracted as objects. Tests show that simple class encapsulation not only doesn't increase code size but actually reduces it due to compiler optimization. For example, encapsulating GPIO registers as a class provides type safety and a better interface while maintaining zero overhead. **Constructors and Destructors (Highly Recommended)** -Constructors provide automatic initialization, and destructors implement the RAII pattern, which is C++'s most powerful resource management mechanism. In embedded systems, destructors can automatically disable peripherals and release resources, preventing resource leaks. Compilers can usually fully inline simple constructors. +Constructors provide automatic initialization, and destructors implement the RAII pattern. This is C++'s most powerful resource management mechanism. In embedded systems, destructors can automatically close peripherals and release resources, avoiding leaks. Compilers can usually fully inline simple constructors. **Templates (Highly Recommended)** -Templates provide compile-time code generation with absolutely zero runtime overhead. The ring buffer test showed that the template version only increased code size by 4%, while providing type safety and size parameterization. Compared to C macros, templates are safer and easier to debug. +Templates provide compile-time code generation with absolutely zero runtime overhead. Ring buffer tests show the template version increases code size by only 4% while providing type safety and size parameterization. Compared to C macros, templates are safer and easier to debug. **constexpr (Highly Recommended)** -constexpr functions are evaluated at compile time, and the results are embedded directly into the code. They can be used for calculating configuration parameters, generating lookup tables, and other scenarios, with completely zero runtime overhead. +`constexpr` functions are calculated at compile time, with results embedded directly in code. They can be used for calculating configuration parameters, lookup table generation, etc., with completely zero runtime overhead. **References and Inline Functions (Highly Recommended)** @@ -1113,43 +581,43 @@ References avoid unnecessary copies, and inline functions eliminate function cal **Operator Overloading (Moderately Recommended)** -Operator overloading can make code more intuitive, such as using ``gpio = true`` instead of ``gpio_write(&gpio, 1)``. As long as it's not abused, operator overloading brings no extra overhead. +Operator overloading makes code more intuitive, for example, using `buffer << data` instead of `buffer_put(data)`. As long as it's not abused, operator overloading incurs no extra cost. ### 3. C++ Features to Use with Caution -The following features have certain overheads and need to be weighed against actual requirements: +The following features have some overhead and need to be weighed against the actual situation: **Virtual Functions (Use with Caution)** -Virtual functions introduce a vtable, adding a 4-byte pointer overhead to each object and requiring an indirect jump for every call. If you truly need polymorphism, consider using CRTP (Curiously Recurring Template Pattern) to achieve compile-time polymorphism and avoid runtime overhead. +Virtual functions introduce a vtable, adding a 4-byte pointer overhead per object and requiring an indirect jump for every call. If you truly need polymorphism, consider using CRTP (Curiously Recurring Template Pattern) to achieve compile-time polymorphism and avoid runtime overhead. **std::function (Use with Caution)** -Tests show that std::function causes 78% code bloat. If you need a callback mechanism, prioritize function pointers (same overhead as C) or template callbacks (zero overhead). Only consider std::function when you need lambdas that capture state. +Tests show `std::function` causes 78% code bloat. If you need a callback mechanism, prioritize function pointers (same overhead as C) or template callbacks (zero overhead). Only consider `std::function` when you need lambdas that capture state. **Dynamic Memory Allocation (Use with Caution)** -new and delete can lead to memory fragmentation in embedded systems. We recommend using placement new with a static memory pool, or using stack-allocated objects. If you must use dynamic memory, consider a custom allocator. +`new` and `delete` can lead to memory fragmentation in embedded systems. It is recommended to use placement new with a static memory pool, or use stack-based objects. If you must use dynamic memory, consider a custom allocator. **STL Containers (Use with Caution)** -Standard library containers like std::vector and std::map can have large implementations. We recommend testing the code size first, or using container libraries specifically optimized for embedded systems (like EASTL). For simple scenarios, hand-writing fixed-size containers might be more appropriate. +Standard library containers like `std::vector` and `std::map` can have large implementations. It is recommended to test code size first or use libraries specifically optimized for embedded systems (like EASTL). For simple scenarios, hand-rolling fixed-size containers might be more appropriate. -### 4. Prohibited C++ Features +### 4. C++ Features to Prohibit The following features should be completely avoided in embedded systems: **Exception Handling (Prohibited)** -The exception handling mechanism increases code size by 50-100% and introduces unpredictable execution paths. Embedded systems require deterministic behavior; use error codes or assertions instead of exceptions. Make sure to add the `-fno-exceptions` compiler flag. +The exception handling mechanism increases code size by 50-100% and introduces unpredictable execution paths. Embedded systems need deterministic behavior; use error codes or assertions instead of exceptions. Always add the `-fno-exceptions` compiler option. **RTTI (Prohibited)** -Run-time type information increases code size by 30-50% and is rarely needed in embedded systems. Disable it with `-fno-rtti`. If you need type identification, you can manually implement a simple type tag system. +Run-Time Type Information increases code size by 30-50% and is rarely needed in embedded systems. Disable with `-fno-rtti`. If type identification is needed, you can manually implement a simple type tag system. **iostream Library (Prohibited)** -std::cout and std::cin introduce massive amounts of code (tens of KB), far beyond what embedded systems can handle. Use traditional printf/scanf or specialized embedded logging libraries instead. +`std::cout` and `std::cin` introduce huge amounts of code (tens of KB), far beyond what an embedded system can bear. Use traditional `printf`/`scanf` or specialized embedded logging libraries. **Multiple Inheritance (Prohibited)** @@ -1157,47 +625,47 @@ Multiple inheritance increases complexity and code size, and can lead to the dia ------ -## Practical Advice: When to Use C, and When to Use C++? +## Practical Advice: When to Use C, When to Use C++? ### Scenarios for Choosing C **Extremely Resource-Constrained Environments** -When the target hardware has less than 8KB of Flash and less than 1KB of RAM, C is the safer choice. These systems are usually simple sensor nodes or controllers that don't need complex abstractions. +When the target hardware has less than 8KB Flash and less than 1KB RAM, C is the safer choice. Such systems are usually simple sensor nodes or controllers that don't require complex abstractions. -**Team Skillset Limitations** +**Team Skill Stack Limitations** -If team members are unfamiliar with C++, or if the project timeline is tight, forcing the use of C++ might do more harm than good. C has a gentler learning curve and is easier to master. +If team members are unfamiliar with C++ or the project timeline is tight, forcing the use of C++ might do more harm than good. C has a gentler learning curve and is easier to master. **Pure C Codebase Integration** -When you need to integrate a large amount of existing C code, using C avoids the hassle of mixed-language programming. Although C++ can call C code, pure C projects are sometimes simpler in certain situations. +When integrating a large amount of existing C code, using C avoids the hassle of mixed programming. Although C++ can call C code, in some cases a pure C project is simpler. **Insufficient Toolchain Support** -Some older or specialized compilers have incomplete C++ support and might generate inefficient code. In these cases, C is the more reliable choice. +Some older or specialized compilers have incomplete C++ support and may produce inefficient code. In this case, C is the more reliable choice. ### Scenarios for Choosing C++ -**Moderately to Highly Resourced Systems** +**Medium to High Resource Systems** -When Flash is greater than 16KB and RAM is greater than 2KB, C++'s advantages start to shine. These systems have enough space to accommodate C++'s abstraction mechanisms while benefiting from encapsulation and type safety. +When Flash is greater than 16KB and RAM is greater than 2KB, C++ advantages start to appear. Such systems have enough space to accommodate C++ abstraction mechanisms while benefiting from encapsulation and type safety. **Complex State Management** When implementing complex logic like state machines, protocol stacks, or sensor fusion, C++ class encapsulation can significantly reduce complexity. Objects can encapsulate state and behavior, making code easier to maintain. -**When Code Reuse is Needed** +**Need Code Reuse** -When you have multiple similar modules (like multiple UARTs or multiple timers), C++ templates are safer and easier to debug than C macros. Templates provide compile-time type checking and parameterization. +When there are multiple similar modules (like multiple UARTs or timers), C++ templates are safer and easier to debug than C macros. Templates provide compile-time type checking and parameterization. **Modern Development Practices** -If the team is familiar with modern C++ (C++11 and later) and can correctly use features like smart pointers, move semantics, and lambdas, development efficiency will significantly improve. +If the team is familiar with modern C++ (C++11 and later) and can correctly use features like smart pointers, move semantics, and lambdas, development efficiency will improve significantly. ### Mixed Usage (Best Practice) -Many successful embedded projects adopt a layered, mixed strategy: +Many successful embedded projects adopt a layered mixed strategy: **Low-Level Driver Layer: Use C** @@ -1205,49 +673,49 @@ Low-level drivers that directly manipulate registers are written in C to ensure **Middle Abstraction Layer: Use C++** -Wrap low-level drivers into C++ classes to provide object-oriented interfaces. For example, wrapping a UART driver into a SerialPort class provides a safer, more user-friendly API. +Wrap low-level drivers into C++ classes to provide an object-oriented interface. For example, wrapping a UART driver as a `SerialPort` class provides a safer, more easy-to-use API. **Application Logic Layer: Use C++** -Business logic, state machines, and data processing are implemented in C++, leveraging features like classes, templates, and RAII to simplify code. +Implement business logic, state machines, and data processing in C++, utilizing features like classes, templates, and RAII to simplify code. -**Module Interfaces: Use extern "C"** +**Module Interfaces: Use `extern "C"`** -Interfaces between modules use `extern "C"` declarations to ensure that C and C++ modules can seamlessly work together. This approach maintains flexibility while avoiding name mangling issues. +Use `extern "C"` declarations for interfaces between modules to ensure C and C++ modules can collaborate seamlessly. This maintains flexibility while avoiding name mangling issues. ------ ## Run Online -Compare the differences in code behavior and sizeof between C and C++ GPIO encapsulation and ring buffers online: +Compare C and C++ GPIO encapsulation and ring buffer differences in code behavior and `sizeof` online: -## Exercise Time: Give It a Try +## Exercise Time: Try It Yourself -### Exercise 1: Hands-on Comparison +### Exercise 1: Actual Measurement Implement the three examples above on your development board and measure: -1. Flash usage (using ``arm-none-eabi-size``) -2. RAM usage (check the .bss and .data sections) -3. Execution time (using the DWT cycle counter) +1. Flash usage (using `size`) +2. RAM usage (check `.bss` and `.data` sections) +3. Execution time (using DWT cycle counter) ### Exercise 2: Optimization Challenge Try optimizing the ring buffer: -1. When Size is a power of two, replace modulo with bitwise operations (``% Size`` → ``& (Size-1)``) -2. Implement a zero-copy ``peek()`` operation -3. Add an interrupt-safe version (by disabling interrupts or using atomic operations) +1. When Size is a power of two, replace modulo with bitwise operations (`% Size` → `& (Size - 1)`) +2. Implement zero-copy `peek` operation +3. Add an interrupt-safe version (disable interrupts or use atomic operations) ### Exercise 3: Design Decisions @@ -1263,36 +731,38 @@ Choose C or C++ for the following scenarios: Find the problems in the following C++ code: ```cpp -class Sensor { - std::vector data; // 问题1: ? +// Bad: Virtual function + std::function + exceptions +class BadButton { public: - virtual void read() { // 问题2: ? - throw std::runtime_error("Not implemented"); // 问题3: ? + virtual void update(bool pressed) { // Virtual function overhead + if (pressed && on_click_) { // std::function overhead + on_click_(); // Potential exception throw + } } + std::function on_click_; // Type erasure overhead }; - ``` -**Improved version**: +**Improved Version**: ```cpp -template -class Sensor { - std::array data_; - size_t size_{0}; +// Good: Static polymorphism + function pointer + no exceptions +class GoodButton { public: - [[nodiscard]] bool read() { // 用bool表示成功/失败 - // 实现... - return true; + using Callback = void(*)(); // Simple function pointer + void update(bool pressed) { + if (pressed && on_click_) { + on_click_(); // No exceptions allowed + } } + Callback on_click_ = nullptr; // No overhead }; - ``` ## Final Words -To quote Bjarne Stroustrup (the creator of C++): +Quoting Bjarne Stroustrup (the father of C++): -> "C++ is not a language that you have to use entirely, but a language that you can choose to use." +> "C++ is not a language you have to use in its entirety, it is a language you can choose to use." -In embedded systems, we need to be smart choosers, not blind followers. Use C++'s powerful features to improve code quality, while steering clear of features that are unsuitable for resource-constrained environments. +In embedded systems, we need to be smart choosers, not blind followers. Use the powerful features of C++ to improve code quality while avoiding those that don't fit in resource-constrained environments. diff --git a/documents/en/vol6-performance/avx-avx2-deep-dive.md b/documents/en/vol6-performance/avx-avx2-deep-dive.md index 5dc8ad656..ea5d39e09 100644 --- a/documents/en/vol6-performance/avx-avx2-deep-dive.md +++ b/documents/en/vol6-performance/avx-avx2-deep-dive.md @@ -1,21 +1,22 @@ --- -title: 'In-Depth Introduction to the AVX Instruction Set Series: Domains, Significance, - and Basic Usage and Examples of AVX / AVX2' -description: '' +chapter: 2 +difficulty: intermediate +order: 3 +platform: host +reading_time_minutes: 6 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 2 -order: 3 +title: 'In-Depth Introduction to the AVX Instruction Set Series: Domains, Significance, + and Basic Usage and Examples of AVX / AVX2' translation: + engine: anthropic source: documents/vol6-performance/avx-avx2-deep-dive.md source_hash: 39da2b3a5a4d6ba1a0e593a1c2fa35355eb91e856ad3d137db96413557586496 - translated_at: '2026-05-26T11:50:35.783290+00:00' - engine: anthropic token_count: 1205 + translated_at: '2026-05-26T11:50:35.783290+00:00' +description: '' --- # An In-Depth Look at the AVX Instruction Set Family: Domains, Significance, and Basic Usage and Examples of AVX / AVX2 diff --git a/documents/en/vol7-engineering/01-cross-compilation-and-cmake.md b/documents/en/vol7-engineering/01-cross-compilation-and-cmake.md index 002cb63f6..0ed0de153 100644 --- a/documents/en/vol7-engineering/01-cross-compilation-and-cmake.md +++ b/documents/en/vol7-engineering/01-cross-compilation-and-cmake.md @@ -12,7 +12,7 @@ order: 1 platform: host prerequisites: - 'Chapter 0: 前言与基础' -reading_time_minutes: 18 +reading_time_minutes: 13 related: [] tags: - cpp-modern @@ -20,11 +20,11 @@ tags: - intermediate title: A Brief Guide to Cross-Compiling and CMake translation: + engine: anthropic source: documents/vol7-engineering/01-cross-compilation-and-cmake.md source_hash: 96f3692b2eef6e172c5a1da5be83edc62dcc598517a8ea4e090d86942419b3b1 - translated_at: '2026-05-26T11:50:30.076667+00:00' - engine: anthropic token_count: 2604 + translated_at: '2026-05-26T11:50:30.076667+00:00' --- # Modern Embedded C++ Tutorial: Cross-Compilation Basics and CMake Multi-Target Builds diff --git a/documents/en/vol7-engineering/01-file-copier-requirements-and-framework.md b/documents/en/vol7-engineering/01-file-copier-requirements-and-framework.md index 47a10893b..6bff23b9d 100644 --- a/documents/en/vol7-engineering/01-file-copier-requirements-and-framework.md +++ b/documents/en/vol7-engineering/01-file-copier-requirements-and-framework.md @@ -1,21 +1,22 @@ --- -title: 'Modern C++ in Practice — Building a File Copier from Scratch (Part 1): Requirements - Analysis and Basic Framework' -description: '' +chapter: 1 +difficulty: intermediate +order: 4 +platform: host +reading_time_minutes: 10 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 1 -order: 4 +title: 'Modern C++ in Practice — Building a File Copier from Scratch (Part 1): Requirements + Analysis and Basic Framework' translation: + engine: anthropic source: documents/vol7-engineering/01-file-copier-requirements-and-framework.md source_hash: f4c115a8025bf912239a5defbdc8200dba0fb92daf51ffc490bc3eecd20a7976 - translated_at: '2026-05-26T11:51:28.325086+00:00' - engine: anthropic token_count: 1336 + translated_at: '2026-05-26T11:51:28.325086+00:00' +description: '' --- # Modern C++ in Practice — Building a File Copier from Scratch (Part 1): Requirements Analysis and Basic Framework diff --git a/documents/en/vol7-engineering/02-compiler-options.md b/documents/en/vol7-engineering/02-compiler-options.md index 2fc7aecdb..ac938a526 100644 --- a/documents/en/vol7-engineering/02-compiler-options.md +++ b/documents/en/vol7-engineering/02-compiler-options.md @@ -5,231 +5,217 @@ cpp_standard: - 14 - 17 - 20 -description: Detailed introduction to common GCC/Clang compiler flags, including language - standards, optimization levels, warning control, and C++ runtime trimming. +description: Detailed introduction to common GCC/Clang compiler options, including + language standards, optimization levels, warning control, and C++ runtime trimming. difficulty: beginner order: 2 platform: host prerequisites: - 'Chapter 0: 前言与基础' -reading_time_minutes: 9 +reading_time_minutes: 7 related: [] tags: - cpp-modern - host - intermediate -title: Common Compiler Flags Guide +title: Guide to Common Compiler Options translation: - source: documents/vol7-engineering/02-compiler-options.md - source_hash: 5c0b5aaeb0b1d04771265b3e2d9c8ba630b8f25acd248bc2fc0c5c6e8106abb4 - translated_at: '2026-05-26T11:51:21.186862+00:00' engine: anthropic - token_count: 1543 + source: documents/vol7-engineering/02-compiler-options.md + source_hash: 3cbac8ed01ae577e224ab152b4ec1d5ceea65745a9a2b5b418cbb10e7a3986ca + token_count: 1542 + translated_at: '2026-06-15T09:31:23.887546+00:00' --- # Modern Embedded C++ Tutorial: Common Compiler Flags Guide -In real-world embedded development, every single byte of Flash and RAM is truly saved by the developer. Although C++ carries the stigma of being a "heavyweight language," we can precisely trim runtime overhead through proper compiler flag configuration, achieving performance and size that even surpass hand-written C code. (I believe you have already seen this in Chapter 0.) +In real-world embedded development, every single byte of Flash and RAM is truly saved by the developer. Although C++ carries the bias of being a "heavyweight language," by configuring compiler flags reasonably, we can precisely trim runtime overhead, achieving performance and size that even surpass hand-written C code. (I believe you have already seen this in Chapter 0). ------ ## 0 Some Basics -#### Language Standard Control: `-std=` +#### Language Standard Control: `-std=c++XX` This is the most direct way to define the "modernity" of a project. -- **Flag format**: `-std=c++11`, `-std=c++14`, `-std=c++17`, `-std=c++20`. -- **GNU extension version**: `gnu++17`. Compared to the standard `c++17`, it allows the use of some GCC-specific non-standard extensions (such as special inline assembly syntax). In low-level embedded development, we sometimes have to use the `gnu++` version. +- **Flag format**: `-std=c++11`, `-std=c++14`, `-std=c++17`, `-std=c++23`. +- **GNU Extension version**: `-std=gnu++XX`. Compared to the standard `-std=c++XX`, it allows using some GCC-specific non-standard extensions (like special inline assembly syntax). In low-level embedded development, we sometimes have to use the `-std=gnu++XX` version. -#### Why Choose `-std=c++17` or Above in Embedded? +#### Why choose `-std=c++17` or above in embedded? -- **The power of `constexpr`**: In C++17, a massive amount of logic can be moved to compile-time calculation, directly reducing runtime CPU load and Flash footprint. -- **`std::span` (C++20)**: It is the perfect replacement for passing buffers in embedded development, offering better safety and zero overhead compared to traditional `uint8_t* ptr, size_t len`. +- **The power of `constexpr`**: In C++17, a large amount of logic can be moved to compile-time calculation, directly reducing runtime CPU load and Flash footprint. +- **`std::span` (C++20)**: It is the perfect replacement for passing buffers in embedded development, safer and with zero overhead compared to traditional raw pointers. - **Structured binding**: Makes parsing complex sensor data structures extremely elegant. ------ -#### Preprocessor and Macro Definitions: `-D` and `-U` +#### Preprocessor and Macros: `-D` and `-U` -In embedded development, due to hardware differences, we frequently need "conditional compilation." +In embedded development, due to hardware differences, we often need "conditional compilation." -- **`-D=`**: Defines a macro. - - For example: `-DSTM32F407xx` or `-DDEBUG_LEVEL=2`. - - **Modern approach**: Try to control this via `target_compile_definitions(target PRIVATE STM32F407xx)` in CMake, rather than littering your code with `#define`. -- **`-U`**: Undefines a previously defined macro. +- **`-D`**: Define a macro. + - Example: `-DDEBUG=1` or `-DSTM32F10X`. + - **Modern practice**: Try to control this via `target_compile_definitions` in CMake rather than filling your code with `#ifdef`. +- **`-U`**: Undefine a defined macro. -> **Warning**: Over-reliance on macros makes code paths difficult to test (Code Coverage cannot cover branches with disabled macros). In modern C++, we recommend prioritizing `if constexpr` combined with constant objects. +> **Warning**: Over-reliance on macros makes code paths difficult to test (Code Coverage cannot cover branches where macros are disabled). In modern C++, it is recommended to prioritize `if constexpr` combined with constant objects. ------ #### Path Search and Library Linking: `-I`, `-isystem`, `-L`, `-l` -This is where beginners most easily make mistakes in CMake configuration. +This is the place where beginners are most prone to configuration errors in CMake. -- **`-I ` (Include)**: Specifies header file search paths. -- **`-isystem `**: Specifies "system" header file paths. - - **The subtlety**: If third-party libraries (like ST's HAL library) generate a lot of meaningless warnings, include them using `-isystem`. The compiler will **automatically suppress all warnings in that directory**, keeping your console clean. -- **`-L `**: Specifies the search directory for static libraries (`.a`). -- **`-l`**: Links a specified library. - - Note: If the library name is `libmath.a`, the flag is `-lmath` (dropping the `lib` prefix and the extension). +- **`-I` (Include)**: Specify header file search paths. +- **`-isystem`**: Specify "system" header file paths. + - **The subtlety**: If a third-party library (like ST's HAL library) generates a lot of meaningless warnings, use `-isystem` to include them. The compiler will **automatically suppress all warnings in that directory**, keeping your console clean. +- **`-L`**: Specify the search directory for static libraries (`.a` files). +- **`-l`**: Link the specified library. + - Note: If the library name is `libfoo.a`, the parameter is `-lfoo` (remove the `lib` prefix and extension). ------ -#### Output Management and Debug Information: `-o` and `-g` +#### Output Management and Debug Info: `-o` and `-g` -- **`-o `**: Specifies the output file name. In cross-compilation, we usually generate an `.elf` file, and then convert it to `.bin` or `.hex` via `objcopy`. +- **`-o`**: Specify the output filename. In cross-compilation, we usually generate an ELF file, and then convert it to HEX or BIN via `objcopy`. - **`-g` and `-g3`**: - - `-g` produces standard debug symbols for GDB (GNU Debugger) debugging. - - **`-g3`**: Even includes debug information for macro definitions. If you need to inspect the value of a certain `#define` during debugging, enable this. - - **Misconception corrected**: Enabling `-g` **does not** increase the size of the code running on the board. Debug information only exists in the `.elf` file on your computer and will not be flashed into the MCU (Microcontroller Unit)'s Flash. + - `-g` produces standard debugging symbols for GDB debugging. + - **`-g3`**: Even includes debug information for macro definitions. If you need to check the value of a specific macro during debugging, turn this on. + - **Misconception corrected**: Enabling `-g` **does not** increase the code size running on the board. Debugging information only exists in the ELF file on your computer and is not flashed into the MCU's Flash. ------ -#### Warning Management: `-W` Series (Code Quality) +#### Warning Management: The `-W` Series (Code Quality) In safety-sensitive fields like embedded systems, warnings are hidden bugs. -- **`-Wall -Wextra`**: The standard for most developers, enabling the vast majority of valuable warnings. -- **`-Werror`**: **Treats all warnings as errors**. - - *Recommended practice*: Enforce `-Werror` in CI/CD (Continuous Integration) environments to ensure submitted code has no hidden dangers. -- **`-Wshadow`**: Issues a warning when a local variable name shadows a global variable name, which is extremely useful when switching embedded logic. -- **`-Wdouble-promotion`**: **A must for embedded!** Warns when you inadvertently promote a `float` to a `double`. On an MCU without a hardware double-precision floating-point unit, this causes a massive performance drop. +- **`-Wall`**: The standard for most developers, enabling most valuable warnings. +- **`-Werror`**: **Treat all warnings as errors**. + - *Recommended practice*: Force `-Werror` in CI/CD (Continuous Integration) environments to ensure submitted code has no hidden dangers. +- **`-Wshadow`**: Warns when a local variable name shadows a global variable name, which is extremely useful during embedded logic switching. +- **`-Wdouble-promotion`**: **Embedded essential!** Warns when you unintentionally promote a `float` to a `double`. On MCUs without double-precision hardware FPU, this leads to a catastrophic drop in performance. ------ -#### Dependency Generation: `-M`, `-MMD` - -Have you ever wondered how CMake knows "because you modified a certain header file, these 10 source files need to be recompiled"? +#### Dependency Generation: `-M`, `-MD` -- **`-MMD`**: Generates a dependency file with a `.d` suffix alongside the compilation. -- **Automation**: Modern build systems (CMake/Ninja) handle these flags automatically. Understanding this helps you troubleshoot incremental compilation issues like "why did my code changes not trigger a recompilation." +Have you ever wondered how CMake knows "since you modified a header file, these 10 source files need to be recompiled"? -```cmake - -# 编译参数 -target_compile_options(${PROJECT_NAME} PRIVATE - -std=c++17 # 核心:定义语言标准 - -g3 # 调试:丰富的调试信息 - -Wall -Wextra # 质量:严格警告 - -Werror # 质量:零容忍警告 - -Wdouble-promotion # 性能:防止隐式双精度运算 - -ffunction-sections # 体积:函数分区 - -fdata-sections # 体积:数据分区 - -fno-exceptions # 裁剪:禁用异常 - -fno-rtti # 裁剪:禁用 RTTI -) - -# 链接参数 -target_link_options(${PROJECT_NAME} PRIVATE - -Wl,--gc-sections # 体积:垃圾回收死代码 - -Wl,-Map=${PROJECT_NAME}.map # 诊断:生成内存映射文件 -) +- **`-MD`**: Generates a dependency relationship file with a `.d` suffix during compilation. +- **Automation**: Modern build systems (CMake/Ninja) handle these options automatically. Understanding this helps you troubleshoot incremental compilation issues like "why didn't the compiler react after I changed the code". +```text +# Example of generated dependencies (foo.o: foo.c foo.h) +main.o: main.cpp config.h hal.hpp ``` ------ ## 1. Optimization Levels: Balancing Speed, Size, and Debugging -GCC and Clang provide multiple levels of optimization switches. Understanding their differences is fundamental for embedded developers. +GCC and Clang provide multi-level optimization switches. Understanding their differences is a fundamental skill for embedded developers. -| **Option** | **Name** | **Core Behavior** | **Use Case** | -| ------------ | -------- | -------------------------------------- | ---------------------------------------- | -| **`-O0`** | No optimization | Maintains a one-to-one correspondence between code and assembly. | Only for tracking down extremely elusive logic bugs. | -| **`-Og`** | Debug optimization | Enables optimizations that do not interfere with debugging observations. | **The top choice for the development phase**, balancing performance with single-step debugging. | -| **`-O2`** | Performance optimization | Enables almost all optimizations that trade space for time. | High-performance computing, RTOS task logic. | -| **`-Os`** | Size optimization | Enables options from `-O2` that do not increase code size. | **The default choice for embedded releases**. | -| **`-Ofast`** | Fast math optimization | Breaks the IEEE 754 standard (does not guarantee floating-point precision). | Pure mathematical calculations where minor precision differences are acceptable. | +| **Option** | **Name** | **Core Behavior** | **Applicable Scenarios** | +| --- | --- | --- | --- | +| **`-O0`** | No optimization | Maintains a one-to-one correspondence between code and assembly. | Only for tracking down extremely difficult logic bugs. | +| **`-Og`** | Debug optimization | Enables optimizations that do not affect debugging observation. | **First choice for development phase**, balancing performance and single-stepping. | +| **`-O2`** | Performance optimization | Enables almost all optimizations that do not trade space for time. | High-performance computing, RTOS task logic. | +| **`-Os`** | Size optimization | Enables options in `-O2` that do not increase code size. | **Default choice for embedded release**. | +| **`-Ofast`** | Fast optimization | Disregards IEEE 754 standard (does not guarantee floating-point precision). | Pure math calculations where minor precision differences are acceptable. | -### 💡 In-Depth Advice: Why Not Use `-O3` in Embedded? +### 💡 Deep Dive: Why not use `-O3` in embedded? -`-O3` performs extensive loop unrolling and function inlining. While speed might improve, on an MCU with severely limited Flash space, it leads to code bloat, and it might even degrade performance due to instruction cache (I-Cache) misses. +`-O3` performs extensive loop unrolling and function inlining. While speed might increase, on MCUs with tight Flash space, it leads to code bloat. It might even degrade performance due to instruction cache (I-Cache) misses. ------ -## 2. Trimming the C++ Runtime: Shedding Heavy "Armor" +## 2. Trimming C++ Runtime: Removing Heavy "Armor" -Modern C++ carries some features by default that come at a high cost in embedded systems. Through the following two flags, we can "slim down" C++ back to an overhead similar to C. +Modern C++ carries some features by default that are extremely expensive in embedded contexts. Through the following two options, we can "slim down" C++ to have overhead similar to C. ### 2.1 `-fno-exceptions` (Disable Exceptions) -- **Cost**: C++ exceptions require massive "unwind table" support, which increases Flash usage by about 10% to 20%. -- **Consequence**: You cannot use `try-catch` and `throw`. If the program encounters an error, it will directly call `std::terminate`. -- **Embedded guideline**: In resource-constrained systems (like Cortex-M), **we strongly recommend disabling this**. +- **Cost**: C++ exceptions require massive "unwind table" support, increasing Flash footprint by about 10%~20%. +- **Consequence**: Cannot use `try` and `catch`. If the program errors, it will directly call `std::terminate`. +- **Embedded guideline**: In resource-constrained systems (like Cortex-M), **strongly recommended to disable**. -### 2.2 `-fno-rtti` (Disable Runtime Type Information) +### 2.2 `-fno-rtti` (Disable Run-Time Type Information) -- **Cost**: To support `dynamic_cast` and `typeid`, the compiler generates extra metadata for every class with virtual functions (information beyond the vtable). -- **Consequence**: You cannot determine an object's true type at runtime. -- **Embedded guideline**: Modern embedded design prefers compile-time polymorphism (templates/CRTP (Curiously Recurring Template Pattern)), making RTTI usually redundant. +- **Cost**: To support `dynamic_cast` and `typeid`, the compiler generates extra metadata (information beyond the vtable) for every class with virtual functions. +- **Consequence**: Cannot determine the real type of an object at runtime. +- **Embedded guideline**: Modern embedded design prefers compile-time polymorphism (templates/CRTP), so RTTI is usually redundant. ------ ## 3. Garbage Collecting Unused Code -By default, the compiler compiles an entire source file into one giant binary block. Even if you only use one function from a library, the linker will stuff the entire library's code into Flash. +By default, the compiler compiles the entire source file into one massive binary block. Even if you only use one function from a library, the linker will stuff the entire library's code into Flash. ### 3.1 Compiler Side: Sectioning -- **`-ffunction-sections`**: Packages each function into its own section. +- **`-ffunction-sections`**: Packages each function independently into a section. - **`-fdata-sections`**: Packages each global/static variable independently. ### 3.2 Linker Side: Garbage Collection -- **`-Wl,--gc-sections`**: Tells the linker (ld) to scan all sections and thoroughly strip out unreferenced "dead code" from the final `.elf` file. +- **`-Wl,--gc-sections`**: Tells the linker (ld) to scan all sections and thoroughly remove "dead code" that is not referenced from the final ELF file. ------ ## 4. Best Practice Configuration in CMake -Translating the above theory into code. In your top-level `CMakeLists.txt`, we recommend managing these flags like this: +Translating the above theory into code. In your top-level `CMakeLists.txt`, we recommend managing these options like this: ```cmake - -# 创建一个专门的编译选项接口库,方便所有 Target 复用 -add_library(project_warnings INTERFACE) - -target_compile_options(project_warnings INTERFACE - $<$:-Os> # Release 模式优化尺寸 - $<$:-Og -g3> # Debug 模式方便调试 - -fno-exceptions # 禁用异常 - -fno-rtti # 禁用 RTTI - -ffunction-sections # 函数分区 - -fdata-sections # 数据分区 - -Wall -Wextra -Wpedantic # 开启严格警告(防患未然) +# 1. Set language standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) # Use pure standard mode, disable GNU extensions + +# 2. Optimization and Debug Symbols +set(CMAKE_CXX_FLAGS_DEBUG "-Og -g") +set(CMAKE_CXX_FLAGS_RELEASE "-Os -DNDEBUG") + +# 3. Compiler Flags +add_compile_options( + -Wall + -Wextra + -Werror + -Wshadow + -Wdouble-promotion + $<$:-fno-exceptions> + $<$:-fno-rtti> + $<$:-fno-threadsafe-statics> # Disable mutex guard for static locals ) -# 链接器选项 -target_link_options(project_warnings INTERFACE - "-Wl,--gc-sections" # 链接时删除死代码 - "--specs=nano.specs" # 使用精简版 C 库 (Newlib-nano) +# 4. Linker Flags (Garbage Collection) +add_link_options( + -Wl,--gc-sections + # If using newlib-nano, specify the lib path + -Wl,--print-memory-usage # Print memory usage report after linking ) - -# 使用时只需要链接这个接口 -target_link_libraries(my_firmware PRIVATE project_warnings) - ``` ------ -## 5. The Dangerous `-Ofast` and Floating-Point Traps +## 5. Dangerous `-Ofast` and Floating-Point Traps In embedded systems, `-Ofast` enables `-ffast-math`. This can lead to: -1. **Precision loss**: To accelerate execution, the compiler might ignore minuscule floating-point errors. +1. **Precision loss**: To speed up execution, the compiler may ignore tiny floating-point errors. 2. **NaN/Inf failure**: It assumes your program will never produce illegal floating-point numbers. -3. **Reordering operations**: This can lead to unstable results in certain algorithms. +3. **Reordering operations**: This can lead to unstable results in some algorithms. -**Recommendation**: Unless you are doing pure digital signal processing (DSP) and have complete control over precision, always stick to using `-Os` or `-O2`. +**Recommendation**: Unless you are doing pure digital signal processing (DSP) and have full control over precision, always stick to `-O2` or `-Os`. ## Run Online -Compare the assembly code generated by the compiler under different optimization levels (-O0 / -Os / -O2) online, and observe the effects of inlining and constant folding: +Compare the assembly code generated by the compiler under different optimization levels (`-O0` / `-Os` / `-O2`) online to observe the effects of inlining and constant folding: ` and `ObserverPtr` from scratch -chapter: 1 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- 智能指针 -- 内存管理 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 20 prerequisites: - 卷二 · 第一章:RAII 深入理解 - 卷二 · 第一章:weak_ptr 与循环引用 +reading_time_minutes: 13 related: - WeakPtr 反模式:T* + raw Flag* 的致命陷阱 -cpp_standard: -- 17 -- 20 +tags: +- host +- cpp-modern +- intermediate +- 智能指针 +- 内存管理 +title: 'A Panorama of Non-Owning Pointers: From T* to Borrowed to ObserverPtr' translation: + engine: anthropic source: documents/vol8-domains/cpp-deep-dives/pointer-semantics/01-non-owning-pointer-overview.md source_hash: 8c3bc7304a09ebee4c8298b6323195b5156a7c9a74dbaf611519bc5a57509c4b - translated_at: '2026-05-26T11:56:01.036850+00:00' - engine: anthropic token_count: 2432 + translated_at: '2026-05-26T11:56:01.036850+00:00' --- # The Non-Owning Pointer Landscape: From T* to Borrowed to ObserverPtr diff --git a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md index 663c126e1..4feee2987 100644 --- a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md +++ b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md @@ -1,31 +1,31 @@ --- -title: 'WeakPtr Anti-Pattern: The Fatal Pitfall of T* + raw Flag*' +chapter: 1 +cpp_standard: +- 17 +- 20 description: Deep dive into why `T*` + raw `Flag*` is not a reliable `WeakPtr`, reproducing UB with a minimal example -chapter: 1 +difficulty: advanced order: 2 +platform: host +prerequisites: +- 非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr +reading_time_minutes: 8 +related: +- SimpleWeakPtr:T* + shared_ptr 的安全改进 tags: - host - cpp-modern - advanced - 智能指针 - 引用计数 -difficulty: advanced -platform: host -reading_time_minutes: 11 -prerequisites: -- 非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr -related: -- SimpleWeakPtr:T* + shared_ptr 的安全改进 -cpp_standard: -- 17 -- 20 +title: 'WeakPtr Anti-Pattern: The Fatal Pitfall of T* + raw Flag*' translation: + engine: anthropic source: documents/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md source_hash: 2077fd44adf00a49be04935895503109025582298fd60609aa03ef7d442d9b9b - translated_at: '2026-05-26T11:54:41.334423+00:00' - engine: anthropic token_count: 1878 + translated_at: '2026-05-26T11:54:41.334423+00:00' --- # The WeakPtr Anti-Pattern: The Fatal Trap of `T* + raw Flag*` diff --git a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md index 5a4942966..ecca1c21b 100644 --- a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md +++ b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md @@ -1,31 +1,31 @@ --- -title: 'SimpleWeakPtr: A Safe Improvement Over T* + shared_ptr' +chapter: 1 +cpp_standard: +- 17 +- 20 description: Building a control block with `shared_ptr` to safely check for null after object destruction. -chapter: 1 +difficulty: intermediate order: 3 +platform: host +prerequisites: +- WeakPtr 反模式:T* + raw Flag* 的致命陷阱 +reading_time_minutes: 6 +related: +- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory tags: - host - cpp-modern - intermediate - 智能指针 - 引用计数 -difficulty: intermediate -platform: host -reading_time_minutes: 8 -prerequisites: -- WeakPtr 反模式:T* + raw Flag* 的致命陷阱 -related: -- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory -cpp_standard: -- 17 -- 20 +title: 'SimpleWeakPtr: A Safe Improvement Over T* + shared_ptr' translation: + engine: anthropic source: documents/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md source_hash: ac358386b001141f476aeef7ffc56fa795fb26b8f4e3dbceaeb6b057bf488327 - translated_at: '2026-05-26T11:55:45.485895+00:00' - engine: anthropic token_count: 1438 + translated_at: '2026-05-26T11:55:45.485895+00:00' --- # SimpleWeakPtr: Safe Improvements with T* + shared_ptr\ diff --git a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md index df8ede2f8..77fc155e9 100644 --- a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md +++ b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md @@ -1,9 +1,18 @@ --- -title: 'Chrome-like WeakPtr: Reference Count Control Block and WeakPtrFactory' +chapter: 1 +cpp_standard: +- 17 +- 20 description: Implement an educational version of Chrome's WeakPtr, and understand the ref-counted control block and sequence binding model -chapter: 1 +difficulty: advanced order: 4 +platform: host +prerequisites: +- SimpleWeakPtr:T* + shared_ptr 的安全改进 +reading_time_minutes: 9 +related: +- std::weak_ptr 对比与异步回调实战 tags: - host - cpp-modern @@ -11,22 +20,13 @@ tags: - 智能指针 - 引用计数 - 回调机制 -difficulty: advanced -platform: host -reading_time_minutes: 12 -prerequisites: -- SimpleWeakPtr:T* + shared_ptr 的安全改进 -related: -- std::weak_ptr 对比与异步回调实战 -cpp_standard: -- 17 -- 20 +title: 'Chrome-like WeakPtr: Reference Count Control Block and WeakPtrFactory' translation: + engine: anthropic source: documents/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md source_hash: c7810fbd0d1980848dcbc7a8559e8c564c7ff5f8a2359266da316380b557d05e - translated_at: '2026-05-26T11:55:12.912926+00:00' - engine: anthropic token_count: 2303 + translated_at: '2026-05-26T11:55:12.912926+00:00' --- # Chrome-like WeakPtr: Reference-Counted Control Block and WeakPtrFactory diff --git a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md index 39575cfe6..cbece6071 100644 --- a/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md +++ b/documents/en/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md @@ -1,9 +1,19 @@ --- -title: '`std::weak_ptr` Comparison and Practical Async Callbacks' +chapter: 1 +cpp_standard: +- 17 +- 20 description: 'Comparing `std::weak_ptr` with Chrome WeakPtr: a safety analysis of six asynchronous callback capture patterns' -chapter: 1 +difficulty: advanced order: 5 +platform: host +prerequisites: +- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory +- 卷二 · 第一章:weak_ptr 与循环引用 +reading_time_minutes: 7 +related: +- 跨线程安全、性能取舍与设计原则总结 tags: - host - cpp-modern @@ -11,23 +21,13 @@ tags: - 智能指针 - 异步编程 - 回调机制 -difficulty: advanced -platform: host -reading_time_minutes: 8 -prerequisites: -- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory -- 卷二 · 第一章:weak_ptr 与循环引用 -related: -- 跨线程安全、性能取舍与设计原则总结 -cpp_standard: -- 17 -- 20 +title: '`std::weak_ptr` Comparison and Practical Async Callbacks' translation: + engine: anthropic source: documents/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md source_hash: e71aa17f860345c3ebdc18ef482f08e7eec3aa5e6fc918fc53381f07184bd56d - translated_at: '2026-05-26T11:56:10.444261+00:00' - engine: anthropic token_count: 1619 + translated_at: '2026-05-26T11:56:10.444261+00:00' --- # std::weak_ptr vs. Chrome WeakPtr and Async Callback Patterns in Practice diff --git a/documents/en/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md b/documents/en/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md index 18055b324..8d9c93a8b 100644 --- a/documents/en/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md +++ b/documents/en/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md @@ -1,21 +1,22 @@ --- -title: 'Part 1: Building an STM32 Development Toolchain from Scratch — Cross-Compilation - Principles and Installation Guide' -description: '' +chapter: 14 +difficulty: beginner +order: 1 +platform: stm32f1 +reading_time_minutes: 11 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 1 +title: 'Part 1: Building an STM32 Development Toolchain from Scratch — Cross-Compilation + Principles and Installation Guide' translation: + engine: anthropic source: documents/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md source_hash: 3e0fe3078ac0320a7d603f5acb8bb8d4d1dce68ee183250e7477e7fc45171b5f - translated_at: '2026-05-26T11:57:24.345125+00:00' - engine: anthropic token_count: 1569 + translated_at: '2026-05-26T11:57:24.345125+00:00' +description: '' --- # Part 1: Building an STM32 Toolchain from Scratch — Cross-Compilation Principles and Installation Guide diff --git a/documents/en/vol8-domains/embedded/00-env-setup/02-project-structure.md b/documents/en/vol8-domains/embedded/00-env-setup/02-project-structure.md index 177cf98e6..5db9e1164 100644 --- a/documents/en/vol8-domains/embedded/00-env-setup/02-project-structure.md +++ b/documents/en/vol8-domains/embedded/00-env-setup/02-project-structure.md @@ -1,21 +1,22 @@ --- -title: 'Part 2: Project Structure — Acquiring the HAL Library, Startup Code Pitfalls, - and Directory Setup' -description: '' +chapter: 14 +difficulty: beginner +order: 2 +platform: stm32f1 +reading_time_minutes: 15 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 2 +title: 'Part 2: Project Structure — Acquiring the HAL Library, Startup Code Pitfalls, + and Directory Setup' translation: + engine: anthropic source: documents/vol8-domains/embedded/00-env-setup/02-project-structure.md source_hash: 42983cc0de29f3716726931f1e2f1c8f784798697d1d4a59b379c1c11fa34fa6 - translated_at: '2026-05-26T11:59:09.043220+00:00' - engine: anthropic token_count: 2359 + translated_at: '2026-05-26T11:59:09.043220+00:00' +description: '' --- # Part 2: Project Structure — Getting the HAL Library, Startup File Pitfalls, and Directory Setup diff --git a/documents/en/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md b/documents/en/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md index 8a1cb8a5d..b4e5a4343 100644 --- a/documents/en/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md +++ b/documents/en/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md @@ -1,20 +1,21 @@ --- -title: CMake Configuration — Building an STM32 Build System from Scratch -description: '' +chapter: 14 +difficulty: beginner +order: 3 +platform: stm32f1 +reading_time_minutes: 17 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 3 +title: CMake Configuration — Building an STM32 Build System from Scratch translation: + engine: anthropic source: documents/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md source_hash: b413fb1eac6642f586a8bb8afe4c0f937d15f7afdcb263c59d5926ae0cbd7f8c - translated_at: '2026-05-26T11:58:16.514187+00:00' - engine: anthropic token_count: 3108 + translated_at: '2026-05-26T11:58:16.514187+00:00' +description: '' --- # CMake Configuration — Building an STM32 Build System from Scratch diff --git a/documents/en/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md b/documents/en/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md index 4a4a764ad..bf25f81d5 100644 --- a/documents/en/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md +++ b/documents/en/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md @@ -1,21 +1,22 @@ --- -title: 'Environment Setup (Part 4): WSL2 USB Passthrough, Letting ST-Link Cross the - Virtualization Boundary' -description: '' +chapter: 14 +difficulty: beginner +order: 4 +platform: stm32f1 +reading_time_minutes: 13 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 4 +title: 'Environment Setup (Part 4): WSL2 USB Passthrough, Letting ST-Link Cross the + Virtualization Boundary' translation: + engine: anthropic source: documents/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md source_hash: 32ae0e014d727cdf7d3bddccce08f45eff977344397e60f1afc0d4bd59a2bb2d - translated_at: '2026-05-26T11:59:41.032466+00:00' - engine: anthropic token_count: 2015 + translated_at: '2026-05-26T11:59:41.032466+00:00' +description: '' --- # Environment Setup (Part 4): WSL2 USB Passthrough — Making ST-Link Cross the Virtualization Boundary diff --git a/documents/en/vol8-domains/embedded/00-env-setup/05-debugging-guide.md b/documents/en/vol8-domains/embedded/00-env-setup/05-debugging-guide.md index 63b231b04..3d6f0e0fa 100644 --- a/documents/en/vol8-domains/embedded/00-env-setup/05-debugging-guide.md +++ b/documents/en/vol8-domains/embedded/00-env-setup/05-debugging-guide.md @@ -1,21 +1,22 @@ --- -title: 'Part 5: Advanced Debugging — From `printf` to a Complete GDB (GNU Debugger) - Environment' -description: '' +chapter: 14 +difficulty: beginner +order: 5 +platform: stm32f1 +reading_time_minutes: 24 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 5 +title: 'Part 5: Advanced Debugging — From `printf` to a Complete GDB (GNU Debugger) + Environment' translation: + engine: anthropic source: documents/vol8-domains/embedded/00-env-setup/05-debugging-guide.md source_hash: c360f75e524dcf5c92f15a767476d520e72f348f144be853cb12fe34781b5b66 - translated_at: '2026-05-26T12:01:18.276291+00:00' - engine: anthropic token_count: 3194 + translated_at: '2026-05-26T12:01:18.276291+00:00' +description: '' --- # Part 5: Advanced Debugging — From printf to a Complete GDB Environment diff --git a/documents/en/vol8-domains/embedded/01-dynamic-allocation-issues.md b/documents/en/vol8-domains/embedded/01-dynamic-allocation-issues.md index 56b427557..5c1ee94cb 100644 --- a/documents/en/vol8-domains/embedded/01-dynamic-allocation-issues.md +++ b/documents/en/vol8-domains/embedded/01-dynamic-allocation-issues.md @@ -11,18 +11,18 @@ order: 1 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 6 +reading_time_minutes: 5 tags: - cpp-modern - intermediate - stm32f1 title: Dynamic Allocation Issues translation: + engine: anthropic source: documents/vol8-domains/embedded/01-dynamic-allocation-issues.md source_hash: 2576910a33d419a77e06cb69a15f53c465165bceaf93afa51db63827e973309b - translated_at: '2026-05-26T11:59:52.455732+00:00' - engine: anthropic token_count: 770 + translated_at: '2026-05-26T11:59:52.455732+00:00' --- # The Cost of Dynamic Memory: Fragmentation and Non-Determinism (Memory Layout, Fragmentation, and Alignment) diff --git a/documents/en/vol8-domains/embedded/01-led/01-motivation-and-overview.md b/documents/en/vol8-domains/embedded/01-led/01-motivation-and-overview.md index 1ff59b3f5..4662ff46a 100644 --- a/documents/en/vol8-domains/embedded/01-led/01-motivation-and-overview.md +++ b/documents/en/vol8-domains/embedded/01-led/01-motivation-and-overview.md @@ -1,20 +1,21 @@ --- -title: 'Part 6: Starting by Lighting the First LED — Why We Use Modern C++ for STM32' -description: '' +chapter: 15 +difficulty: beginner +order: 1 +platform: stm32f1 +reading_time_minutes: 22 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 1 +title: 'Part 6: Starting by Lighting the First LED — Why We Use Modern C++ for STM32' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/01-motivation-and-overview.md source_hash: 893dbb72425c37cfd9fd38832830d09b5e374ae7eb44c4ca124ac050999ae2ca - translated_at: '2026-05-26T12:03:11.907957+00:00' - engine: anthropic token_count: 2498 + translated_at: '2026-05-26T12:03:11.907957+00:00' +description: '' --- # Part 6: Lighting the First LED — Why We Use Modern C++ for STM32 diff --git a/documents/en/vol8-domains/embedded/01-led/02-what-is-gpio.md b/documents/en/vol8-domains/embedded/01-led/02-what-is-gpio.md index 7d818a329..0b2e7672f 100644 --- a/documents/en/vol8-domains/embedded/01-led/02-what-is-gpio.md +++ b/documents/en/vol8-domains/embedded/01-led/02-what-is-gpio.md @@ -1,20 +1,21 @@ --- -title: 'Part 7: What Exactly Is GPIO — The Past and Present of General-Purpose I/O' -description: '' +chapter: 15 +difficulty: beginner +order: 2 +platform: stm32f1 +reading_time_minutes: 25 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 2 +title: 'Part 7: What Exactly Is GPIO — The Past and Present of General-Purpose I/O' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/02-what-is-gpio.md source_hash: 79c605428f81de109fd4a5e27145a4b3e526a891c4de00b21cded7c27749ad70 - translated_at: '2026-05-26T12:02:23.869175+00:00' - engine: anthropic token_count: 2841 + translated_at: '2026-05-26T12:02:23.869175+00:00' +description: '' --- # Part 7: What Exactly Is GPIO — The Past and Present of General-Purpose I/O diff --git a/documents/en/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md b/documents/en/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md index 01bddcf25..1abb3b6bf 100644 --- a/documents/en/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md +++ b/documents/en/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md @@ -1,21 +1,22 @@ --- -title: 'Part 8: Push-Pull, Open-Drain, and PC13 — The Hardware Secrets Behind Lighting - an LED' -description: '' +chapter: 15 +difficulty: beginner +order: 3 +platform: stm32f1 +reading_time_minutes: 24 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 3 +title: 'Part 8: Push-Pull, Open-Drain, and PC13 — The Hardware Secrets Behind Lighting + an LED' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md source_hash: dd1b84c08aff841749581b7323b6c86db93d07c3534e355731acb7c188617f5a - translated_at: '2026-05-26T12:05:31.367982+00:00' - engine: anthropic token_count: 2602 + translated_at: '2026-05-26T12:05:31.367982+00:00' +description: '' --- # Part 8: Push-Pull, Open-Drain, and PC13 — The Hardware Secrets Behind Lighting an LED diff --git a/documents/en/vol8-domains/embedded/01-led/04-hal-gpio-clock.md b/documents/en/vol8-domains/embedded/01-led/04-hal-gpio-clock.md index c2699bd73..e84674840 100644 --- a/documents/en/vol8-domains/embedded/01-led/04-hal-gpio-clock.md +++ b/documents/en/vol8-domains/embedded/01-led/04-hal-gpio-clock.md @@ -1,21 +1,22 @@ --- -title: 'Part 9: HAL Clock Enable — Without a Clock, a Peripheral Is Just a Piece of - Dormant Silicon' -description: '' +chapter: 15 +difficulty: beginner +order: 4 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 4 +title: 'Part 9: HAL Clock Enable — Without a Clock, a Peripheral Is Just a Piece of + Dormant Silicon' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/04-hal-gpio-clock.md source_hash: c328592f0ef1a42e41e289bbdb550851ade7ae452259f0d790a27f3ebefc5f56 - translated_at: '2026-05-26T12:04:39.485053+00:00' - engine: anthropic token_count: 2834 + translated_at: '2026-05-26T12:04:39.485053+00:00' +description: '' --- # Part 9: HAL Clock Enable — Without a Clock, a Peripheral Is Just a Dead Piece of Silicon diff --git a/documents/en/vol8-domains/embedded/01-led/05-hal-gpio-init.md b/documents/en/vol8-domains/embedded/01-led/05-hal-gpio-init.md index d8561716b..215c4e4af 100644 --- a/documents/en/vol8-domains/embedded/01-led/05-hal-gpio-init.md +++ b/documents/en/vol8-domains/embedded/01-led/05-hal-gpio-init.md @@ -1,20 +1,21 @@ --- -title: 'Part 10: HAL_GPIO_Init — The Ritual of Telling the Chip About Pin Configurations' -description: '' +chapter: 15 +difficulty: beginner +order: 5 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 5 +title: 'Part 10: HAL_GPIO_Init — The Ritual of Telling the Chip About Pin Configurations' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/05-hal-gpio-init.md source_hash: 072f6cbaf94f8bf45298818233d9e9c74661fe24b4b350037a2e52a96b8838b9 - translated_at: '2026-05-26T12:05:21.215210+00:00' - engine: anthropic token_count: 3062 + translated_at: '2026-05-26T12:05:21.215210+00:00' +description: '' --- # Part 10: HAL_GPIO_Init — The Ritual of Telling the Chip How to Configure Its Pins diff --git a/documents/en/vol8-domains/embedded/01-led/06-hal-gpio-output.md b/documents/en/vol8-domains/embedded/01-led/06-hal-gpio-output.md index 293839d86..3b36f179b 100644 --- a/documents/en/vol8-domains/embedded/01-led/06-hal-gpio-output.md +++ b/documents/en/vol8-domains/embedded/01-led/06-hal-gpio-output.md @@ -1,20 +1,21 @@ --- -title: 'Part 11: HAL_GPIO_WritePin and TogglePin — Making Pins Move' -description: '' +chapter: 15 +difficulty: beginner +order: 6 +platform: stm32f1 +reading_time_minutes: 69 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 6 +title: 'Part 11: HAL_GPIO_WritePin and TogglePin — Making Pins Move' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/06-hal-gpio-output.md source_hash: 9029ec886405ec08b786bcb253e720291383e378eaf2f18822358253ea877431 - translated_at: '2026-05-26T12:08:11.905578+00:00' - engine: anthropic token_count: 1542 + translated_at: '2026-05-26T12:08:11.905578+00:00' +description: '' --- # Part 11: HAL_GPIO_WritePin and TogglePin — Making Pins Move diff --git a/documents/en/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md b/documents/en/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md index 95eb8ed45..faf911090 100644 --- a/documents/en/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md +++ b/documents/en/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md @@ -1,20 +1,21 @@ --- -title: 'Part 12: LED Drivers in the C Macro Era — Works, But Not Elegant' -description: '' +chapter: 15 +difficulty: beginner +order: 7 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 7 +title: 'Part 12: LED Drivers in the C Macro Era — Works, But Not Elegant' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md source_hash: c964b0dca0544a4195b04c0cce76c6064075cbe1983c3fcbd1fd11237e9983af - translated_at: '2026-05-26T12:07:22.220897+00:00' - engine: anthropic token_count: 2561 + translated_at: '2026-05-26T12:07:22.220897+00:00' +description: '' --- # Part 12: LED Drivers in the C Macro Era — It Works, But It Isn't Elegant diff --git a/documents/en/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md b/documents/en/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md index 5c251048a..a0bf0e156 100644 --- a/documents/en/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md +++ b/documents/en/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md @@ -1,21 +1,22 @@ --- -title: 'Part 13: First Refactoring — Replacing Macros with enum class, the Start of - Type Safety' -description: '' +chapter: 15 +difficulty: beginner +order: 8 +platform: stm32f1 +reading_time_minutes: 9 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 8 +title: 'Part 13: First Refactoring — Replacing Macros with enum class, the Start of + Type Safety' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md source_hash: f8b0472a1e1d9b03fb216f2cfc73b47927cf313fc0292523e1115842cca9cdb7 - translated_at: '2026-05-26T12:06:12.539347+00:00' - engine: anthropic token_count: 1490 + translated_at: '2026-05-26T12:06:12.539347+00:00' +description: '' --- # Part 13: The First Refactor — Replacing Macros with enum class, the Start of Type Safety diff --git a/documents/en/vol8-domains/embedded/01-led/09-cpp-template-gpio.md b/documents/en/vol8-domains/embedded/01-led/09-cpp-template-gpio.md index cbe8f162f..333fbd6e6 100644 --- a/documents/en/vol8-domains/embedded/01-led/09-cpp-template-gpio.md +++ b/documents/en/vol8-domains/embedded/01-led/09-cpp-template-gpio.md @@ -1,21 +1,22 @@ --- -title: 'Part 14: The Second Refactoring — Templates Take the Stage, Binding Ports - and Pins at Compile Time' -description: '' +chapter: 15 +difficulty: beginner +order: 9 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 9 +title: 'Part 14: The Second Refactoring — Templates Take the Stage, Binding Ports + and Pins at Compile Time' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/09-cpp-template-gpio.md source_hash: 6c7ad77aaa2c8f4086c204810c3cfeca22f46d8779d785ba69e4e466c078c9f3 - translated_at: '2026-05-26T12:07:06.667965+00:00' - engine: anthropic token_count: 1308 + translated_at: '2026-05-26T12:07:06.667965+00:00' +description: '' --- # Part 14: The Second Refactoring — Templates Take the Stage, Binding Ports and Pins at Compile Time diff --git a/documents/en/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md b/documents/en/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md index c636a8ac8..6a83a6991 100644 --- a/documents/en/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md +++ b/documents/en/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md @@ -1,21 +1,22 @@ --- -title: 'Part 15: The Third Refactor — Using `if constexpr` to Automatically Select - the Right Clock Enable at Compile Time' -description: '' +chapter: 15 +difficulty: beginner +order: 10 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 10 +title: 'Part 15: The Third Refactor — Using `if constexpr` to Automatically Select + the Right Clock Enable at Compile Time' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md source_hash: 46566194d11d1983b4a63adaf02f386ff3ba4f1984048233c29a68999773f466 - translated_at: '2026-05-26T12:07:50.338434+00:00' - engine: anthropic token_count: 1426 + translated_at: '2026-05-26T12:07:50.338434+00:00' +description: '' --- # Part 15: The Third Refactoring — Using `if constexpr` to Automatically Select Clock Enable at Compile Time diff --git a/documents/en/vol8-domains/embedded/01-led/11-cpp-led-template.md b/documents/en/vol8-domains/embedded/01-led/11-cpp-led-template.md index f1c11537c..bbfafec1b 100644 --- a/documents/en/vol8-domains/embedded/01-led/11-cpp-led-template.md +++ b/documents/en/vol8-domains/embedded/01-led/11-cpp-led-template.md @@ -1,21 +1,22 @@ --- -title: 'Part 16: Fourth Refactoring — LED Template, From Generic GPIO to Dedicated - Abstraction' -description: '' +chapter: 15 +difficulty: beginner +order: 11 +platform: stm32f1 +reading_time_minutes: 26 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 11 +title: 'Part 16: Fourth Refactoring — LED Template, From Generic GPIO to Dedicated + Abstraction' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/11-cpp-led-template.md source_hash: 9915cc35c5ee69b992b1824124e63a5a09b4cc744a9f0d3562a1f780a16107f0 - translated_at: '2026-05-26T12:09:11.400230+00:00' - engine: anthropic token_count: 4388 + translated_at: '2026-05-26T12:09:11.400230+00:00' +description: '' --- # Part 16: Fourth Refactoring — The LED Template, From Generic GPIO to Domain-Specific Abstraction diff --git a/documents/en/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md b/documents/en/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md index b9d672348..ede7b5aa8 100644 --- a/documents/en/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md +++ b/documents/en/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md @@ -1,21 +1,22 @@ --- -title: 'Part 17: Wrapping Up C++23 Features — Attributes, Linkage, and the Final Proof - of Zero-Overhead Abstraction' -description: '' +chapter: 15 +difficulty: beginner +order: 12 +platform: stm32f1 +reading_time_minutes: 10 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 12 +title: 'Part 17: Wrapping Up C++23 Features — Attributes, Linkage, and the Final Proof + of Zero-Overhead Abstraction' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md source_hash: e7358ff02a99ecd65da0e19a2de45ac94ed699905fee5cbb14694278d1123acb - translated_at: '2026-05-26T12:08:58.330486+00:00' - engine: anthropic token_count: 1810 + translated_at: '2026-05-26T12:08:58.330486+00:00' +description: '' --- # Part 17: Wrapping Up C++23 Features — Attributes, Linkage, and the Final Proof of Zero-Overhead Abstraction diff --git a/documents/en/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md b/documents/en/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md index d782d3ba4..b773d1c3b 100644 --- a/documents/en/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md +++ b/documents/en/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md @@ -1,20 +1,21 @@ --- -title: 'Part 18: Common Pitfalls and Hands-on Practice — Having Fun with LEDs' -description: '' +chapter: 15 +difficulty: beginner +order: 13 +platform: stm32f1 +reading_time_minutes: 10 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 13 +title: 'Part 18: Common Pitfalls and Hands-on Practice — Having Fun with LEDs' translation: + engine: anthropic source: documents/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md source_hash: 20e7dc756e285ab3e206f725de1e1671030edd9ad71cc5a68265e84106b1645b - translated_at: '2026-05-26T12:08:49.663261+00:00' - engine: anthropic token_count: 1922 + translated_at: '2026-05-26T12:08:49.663261+00:00' +description: '' --- # Part 18: Common Pitfalls and Practical Exercises — Getting Creative with LEDs diff --git a/documents/en/vol8-domains/embedded/01-resource-and-realtime-constraints.md b/documents/en/vol8-domains/embedded/01-resource-and-realtime-constraints.md index 97f117881..e5413f5d5 100644 --- a/documents/en/vol8-domains/embedded/01-resource-and-realtime-constraints.md +++ b/documents/en/vol8-domains/embedded/01-resource-and-realtime-constraints.md @@ -11,7 +11,7 @@ difficulty: beginner order: 1 platform: stm32f1 prerequisites: [] -reading_time_minutes: 12 +reading_time_minutes: 10 related: [] tags: - cpp-modern @@ -19,11 +19,11 @@ tags: - stm32f1 title: Resources and Real-Time Constraints in Embedded Systems translation: + engine: anthropic source: documents/vol8-domains/embedded/01-resource-and-realtime-constraints.md source_hash: 5a292bda5a45a4c180240381379f3a89495651476a96e01b29856cce67b4dcc0 - translated_at: '2026-05-26T12:09:35.449598+00:00' - engine: anthropic token_count: 1514 + translated_at: '2026-05-26T12:09:35.449598+00:00' --- # Embedded Resource and Real-Time Constraints diff --git a/documents/en/vol8-domains/embedded/01-zero-overhead-abstraction.md b/documents/en/vol8-domains/embedded/01-zero-overhead-abstraction.md index d90b04970..3e652eed2 100644 --- a/documents/en/vol8-domains/embedded/01-zero-overhead-abstraction.md +++ b/documents/en/vol8-domains/embedded/01-zero-overhead-abstraction.md @@ -11,18 +11,18 @@ order: 1 platform: stm32f1 prerequisites: - 'Chapter 1: 构建工具链' -reading_time_minutes: 19 +reading_time_minutes: 15 tags: - cpp-modern - intermediate - stm32f1 title: Zero-Overhead Abstraction translation: + engine: anthropic source: documents/vol8-domains/embedded/01-zero-overhead-abstraction.md source_hash: 03af5e7165b9cd5be865db1fc88856b77044d27ba20f47537356944291f35c6d - translated_at: '2026-05-26T12:10:33.521058+00:00' - engine: anthropic token_count: 2369 + translated_at: '2026-05-26T12:10:33.521058+00:00' --- # Modern C++ for Embedded Systems—Zero-Overhead Abstraction diff --git a/documents/en/vol8-domains/embedded/02-button/01-from-output-to-input.md b/documents/en/vol8-domains/embedded/02-button/01-from-output-to-input.md index a42cf1a00..1cbb89fb4 100644 --- a/documents/en/vol8-domains/embedded/02-button/01-from-output-to-input.md +++ b/documents/en/vol8-domains/embedded/02-button/01-from-output-to-input.md @@ -1,20 +1,21 @@ --- -title: 'Part 19: From Output to Input — Why Buttons Are Harder Than LEDs' -description: '' +chapter: 16 +difficulty: intermediate +order: 1 +platform: stm32f1 +reading_time_minutes: 11 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 1 +title: 'Part 19: From Output to Input — Why Buttons Are Harder Than LEDs' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/01-from-output-to-input.md source_hash: 03b7c2f10cb4887ce6c0fdaa24de8f6bcc02127b033270480bd2ddc102ff1764 - translated_at: '2026-05-26T12:10:20.197367+00:00' - engine: anthropic token_count: 1416 + translated_at: '2026-05-26T12:10:20.197367+00:00' +description: '' --- # Part 19: From Output to Input — Why Buttons Are Harder Than LEDs diff --git a/documents/en/vol8-domains/embedded/02-button/02-gpio-input-circuits.md b/documents/en/vol8-domains/embedded/02-button/02-gpio-input-circuits.md index 5dda07950..878cd4848 100644 --- a/documents/en/vol8-domains/embedded/02-button/02-gpio-input-circuits.md +++ b/documents/en/vol8-domains/embedded/02-button/02-gpio-input-circuits.md @@ -1,21 +1,22 @@ --- -title: 'Part 20: GPIO Input Mode Internal Circuitry — How a Chip "Hears" External - Signals' -description: '' +chapter: 16 +difficulty: intermediate +order: 2 +platform: stm32f1 +reading_time_minutes: 11 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 2 +title: 'Part 20: GPIO Input Mode Internal Circuitry — How a Chip "Hears" External + Signals' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/02-gpio-input-circuits.md source_hash: d4e4ec66429dc36883a0a6e9b8dfad40ad6992e442aed89c509848b85d20f512 - translated_at: '2026-05-26T12:11:11.087047+00:00' - engine: anthropic token_count: 1600 + translated_at: '2026-05-26T12:11:11.087047+00:00' +description: '' --- # Part 20: GPIO Input Mode Internal Circuitry — How the Chip "Hears" External Signals diff --git a/documents/en/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md b/documents/en/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md index 7c77be86d..6b00ce15c 100644 --- a/documents/en/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md +++ b/documents/en/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md @@ -1,21 +1,22 @@ --- -title: 'Part 21: Button Circuits and Mechanical Bounce — What Do Real-World Signals - Look Like?' -description: '' +chapter: 16 +difficulty: intermediate +order: 3 +platform: stm32f1 +reading_time_minutes: 10 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 3 +title: 'Part 21: Button Circuits and Mechanical Bounce — What Do Real-World Signals + Look Like?' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md source_hash: b9992baf1af2425bd8b83ef661eeb50fa7fc4cd9b2e2294dc61d57248bb93c48 - translated_at: '2026-05-26T12:11:06.404702+00:00' - engine: anthropic token_count: 1461 + translated_at: '2026-05-26T12:11:06.404702+00:00' +description: '' --- # Part 21: Button Circuits and Mechanical Bounce — What Real-World Signals Look Like diff --git a/documents/en/vol8-domains/embedded/02-button/04-hal-gpio-input.md b/documents/en/vol8-domains/embedded/02-button/04-hal-gpio-input.md index 25eecaffd..0f32d1c04 100644 --- a/documents/en/vol8-domains/embedded/02-button/04-hal-gpio-input.md +++ b/documents/en/vol8-domains/embedded/02-button/04-hal-gpio-input.md @@ -1,20 +1,21 @@ --- -title: 'Part 22: HAL GPIO Input API — How to Read Button State in Code' -description: '' +chapter: 16 +difficulty: intermediate +order: 4 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 4 +title: 'Part 22: HAL GPIO Input API — How to Read Button State in Code' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/04-hal-gpio-input.md source_hash: d9beddcba6146789438c19cb77e80a3a009af89874ed226ff9c3adb560384f48 - translated_at: '2026-05-26T12:11:07.797286+00:00' - engine: anthropic token_count: 1531 + translated_at: '2026-05-26T12:11:07.797286+00:00' +description: '' --- # Part 22: HAL GPIO Input API — How to Read Button State in Code diff --git a/documents/en/vol8-domains/embedded/02-button/05-c-polling-button.md b/documents/en/vol8-domains/embedded/02-button/05-c-polling-button.md index 9bcff11b8..73a5aa35f 100644 --- a/documents/en/vol8-domains/embedded/02-button/05-c-polling-button.md +++ b/documents/en/vol8-domains/embedded/02-button/05-c-polling-button.md @@ -1,21 +1,22 @@ --- -title: 'Part篇 23: Polling Buttons in C — Your First Time Controlling an LED with a - Button' -description: '' +chapter: 16 +difficulty: intermediate +order: 5 +platform: stm32f1 +reading_time_minutes: 10 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 5 +title: 'Part篇 23: Polling Buttons in C — Your First Time Controlling an LED with a + Button' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/05-c-polling-button.md source_hash: e0b865c83a896f18c013c16013f418aecdd2aa531cc5438c68a323a299953b9c - translated_at: '2026-05-26T12:12:14.955131+00:00' - engine: anthropic token_count: 1844 + translated_at: '2026-05-26T12:12:14.955131+00:00' +description: '' --- # Part 23: C Language Button Polling — Making a Button Control an LED for the First Time diff --git a/documents/en/vol8-domains/embedded/02-button/06-non-blocking-debounce.md b/documents/en/vol8-domains/embedded/02-button/06-non-blocking-debounce.md index fe7fac277..5a6ed0b62 100644 --- a/documents/en/vol8-domains/embedded/02-button/06-non-blocking-debounce.md +++ b/documents/en/vol8-domains/embedded/02-button/06-non-blocking-debounce.md @@ -1,20 +1,21 @@ --- -title: 'Part 24: Non-blocking Debounce — Keeping the CPU Moving' -description: '' +chapter: 16 +difficulty: intermediate +order: 6 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 6 +title: 'Part 24: Non-blocking Debounce — Keeping the CPU Moving' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/06-non-blocking-debounce.md source_hash: b8b0050f3de67179929f301036d8a697948234f130a92539eda697170010ed3f - translated_at: '2026-05-26T12:11:27.712481+00:00' - engine: anthropic token_count: 1479 + translated_at: '2026-05-26T12:11:27.712481+00:00' +description: '' --- # Part 24: Non-blocking Debounce — Don't Make the CPU Wait diff --git a/documents/en/vol8-domains/embedded/02-button/07-debounce-state-machine.md b/documents/en/vol8-domains/embedded/02-button/07-debounce-state-machine.md index 5ae8bc1c8..eca755db3 100644 --- a/documents/en/vol8-domains/embedded/02-button/07-debounce-state-machine.md +++ b/documents/en/vol8-domains/embedded/02-button/07-debounce-state-machine.md @@ -1,20 +1,21 @@ --- -title: 'Part 25: 7-State Debounce State Machine — The Core of This Series' -description: '' +chapter: 16 +difficulty: intermediate +order: 7 +platform: stm32f1 +reading_time_minutes: 9 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 7 +title: 'Part 25: 7-State Debounce State Machine — The Core of This Series' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/07-debounce-state-machine.md source_hash: 105b11e7d7d22ab9859d953c12c557cb4ea19c1f0acfb7e058f1315261c60efd - translated_at: '2026-05-26T12:12:26.917508+00:00' - engine: anthropic token_count: 1974 + translated_at: '2026-05-26T12:12:26.917508+00:00' +description: '' --- # Part 25: The 7-State Debounce State Machine — The Core of This Series diff --git a/documents/en/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md b/documents/en/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md index 86cb4b121..f5e964171 100644 --- a/documents/en/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md +++ b/documents/en/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md @@ -1,20 +1,21 @@ --- -title: 'Part 26: Refactoring Button Code with `enum class` — Type-Safe Input' -description: '' +chapter: 16 +difficulty: intermediate +order: 8 +platform: stm32f1 +reading_time_minutes: 4 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 8 +title: 'Part 26: Refactoring Button Code with `enum class` — Type-Safe Input' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md source_hash: 337e2f76412a48cf19054b4e6fe3e9bb6509ddd460785ff053c0788bc68cb38c - translated_at: '2026-05-26T12:11:54.253568+00:00' - engine: anthropic token_count: 795 + translated_at: '2026-05-26T12:11:54.253568+00:00' +description: '' --- # Part 26: `enum class` Refactoring Button Code — Type-Safe Input diff --git a/documents/en/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md b/documents/en/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md index b3abc05b6..6b461d5bb 100644 --- a/documents/en/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md +++ b/documents/en/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md @@ -1,21 +1,22 @@ --- -title: 'Part 27: `std::variant` Events + `std::visit` Dispatching — Type-Safe "What - Happened' -description: '' +chapter: 16 +difficulty: intermediate +order: 9 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 9 +title: 'Part 27: `std::variant` Events + `std::visit` Dispatching — Type-Safe "What + Happened' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md source_hash: 2f20607a3461c1bef610a9bc65e1e753cea59261432d5b95c424bf48b37c7bdd - translated_at: '2026-05-26T12:12:30.326573+00:00' - engine: anthropic token_count: 1475 + translated_at: '2026-05-26T12:12:30.326573+00:00' +description: '' --- # Part 27: `std::variant` Events + `std::visit` Dispatch — Type-Safe "What Happened" diff --git a/documents/en/vol8-domains/embedded/02-button/10-cpp-template-button.md b/documents/en/vol8-domains/embedded/02-button/10-cpp-template-button.md index bf98f2d0e..031eec846 100644 --- a/documents/en/vol8-domains/embedded/02-button/10-cpp-template-button.md +++ b/documents/en/vol8-domains/embedded/02-button/10-cpp-template-button.md @@ -1,20 +1,21 @@ --- -title: 'Part 28: Button Template Class Design — Leave Everything to the Compiler' -description: '' +chapter: 16 +difficulty: intermediate +order: 10 +platform: stm32f1 +reading_time_minutes: 6 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 10 +title: 'Part 28: Button Template Class Design — Leave Everything to the Compiler' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/10-cpp-template-button.md source_hash: e35b56f1419e97e2e177ab5a9209d7a2ee0b9904049a578cab236be1b412754c - translated_at: '2026-05-26T12:12:57.693642+00:00' - engine: anthropic token_count: 1486 + translated_at: '2026-05-26T12:12:57.693642+00:00' +description: '' --- # Part 28: Designing a Button Template Class — Letting the Compiler Handle Everything diff --git a/documents/en/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md b/documents/en/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md index 529a2f33b..160be9753 100644 --- a/documents/en/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md +++ b/documents/en/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md @@ -1,20 +1,21 @@ --- -title: 'Part 29: Concepts-Constrained Callbacks + Full Code Walkthrough' -description: '' +chapter: 16 +difficulty: intermediate +order: 11 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 11 +title: 'Part 29: Concepts-Constrained Callbacks + Full Code Walkthrough' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md source_hash: 56a5f9b60153686ab7b7d6e6275112703ed0264ef593a52fb03f311eb7c79d4c - translated_at: '2026-05-26T12:13:42.633301+00:00' - engine: anthropic token_count: 1595 + translated_at: '2026-05-26T12:13:42.633301+00:00' +description: '' --- # Part 29: Constraining Callbacks with Concepts + Full Code Walkthrough diff --git a/documents/en/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md b/documents/en/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md index b7f7ef9cb..27e90449a 100644 --- a/documents/en/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md +++ b/documents/en/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md @@ -1,20 +1,21 @@ --- -title: 'Part 30: EXTI Interrupts + Pitfalls and Exercises' -description: '' +chapter: 16 +difficulty: intermediate +order: 12 +platform: stm32f1 +reading_time_minutes: 11 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 12 +title: 'Part 30: EXTI Interrupts + Pitfalls and Exercises' translation: + engine: anthropic source: documents/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md source_hash: 7eb1b5506e65effac513f672453928868bec0ad943d2d45203153069bdca1d3b - translated_at: '2026-05-26T12:13:12.510434+00:00' - engine: anthropic token_count: 1703 + translated_at: '2026-05-26T12:13:12.510434+00:00' +description: '' --- # Part 30: EXTI Interrupts + Pitfalls and Exercises diff --git a/documents/en/vol8-domains/embedded/02-static-and-stack-allocation.md b/documents/en/vol8-domains/embedded/02-static-and-stack-allocation.md index 4e72647a8..59872b0c4 100644 --- a/documents/en/vol8-domains/embedded/02-static-and-stack-allocation.md +++ b/documents/en/vol8-domains/embedded/02-static-and-stack-allocation.md @@ -11,18 +11,18 @@ order: 2 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 6 +reading_time_minutes: 5 tags: - cpp-modern - intermediate - stm32f1 title: Static Storage and Stack Allocation Strategies translation: + engine: anthropic source: documents/vol8-domains/embedded/02-static-and-stack-allocation.md source_hash: 0bb24db10c20e5193c9c6ffa4a6f150ebcd75c7e178d62e963655b09bd693b87 - translated_at: '2026-05-26T12:13:46.076201+00:00' - engine: anthropic token_count: 920 + translated_at: '2026-05-26T12:13:46.076201+00:00' --- # Embedded C++ Tutorial — Static Storage and Stack Allocation Strategies diff --git a/documents/en/vol8-domains/embedded/02-type-safe-register-access.md b/documents/en/vol8-domains/embedded/02-type-safe-register-access.md index a3a2c07cc..32f1ec921 100644 --- a/documents/en/vol8-domains/embedded/02-type-safe-register-access.md +++ b/documents/en/vol8-domains/embedded/02-type-safe-register-access.md @@ -11,18 +11,18 @@ order: 2 platform: stm32f1 prerequisites: - 'Chapter 7: 容器与数据结构' -reading_time_minutes: 7 +reading_time_minutes: 4 tags: - cpp-modern - stm32f1 - intermediate title: Type-Safe Register Access translation: + engine: anthropic source: documents/vol8-domains/embedded/02-type-safe-register-access.md source_hash: 01e1bfe6b9c623aff34bb3e910c4abf01ca82e1b62702ccfe41fab167c2923f9 - translated_at: '2026-06-14T00:20:46.752858+00:00' - engine: anthropic token_count: 1107 + translated_at: '2026-06-14T00:20:46.752858+00:00' --- # Embedded C++ Tutorial — Type-Safe Register Access diff --git a/documents/en/vol8-domains/embedded/03-circular-buffer.md b/documents/en/vol8-domains/embedded/03-circular-buffer.md index fff3f32c1..a942d4639 100644 --- a/documents/en/vol8-domains/embedded/03-circular-buffer.md +++ b/documents/en/vol8-domains/embedded/03-circular-buffer.md @@ -11,18 +11,18 @@ order: 3 platform: stm32f1 prerequisites: - 'Chapter 6: RAII与智能指针' -reading_time_minutes: 6 +reading_time_minutes: 4 tags: - cpp-modern - stm32f1 - intermediate title: Circular Buffer Implementation translation: + engine: anthropic source: documents/vol8-domains/embedded/03-circular-buffer.md source_hash: 8c134e19ee132d94c025e8b4c70083d7d6ca8206d7b828f8d8fb6396ee391a86 - translated_at: '2026-06-14T00:20:56.830619+00:00' - engine: anthropic token_count: 981 + translated_at: '2026-06-14T00:20:56.830619+00:00' --- # Embedded C++ Tutorial — Circular Buffer diff --git a/documents/en/vol8-domains/embedded/03-object-pool-pattern.md b/documents/en/vol8-domains/embedded/03-object-pool-pattern.md index a2f975aa7..a11a85bb2 100644 --- a/documents/en/vol8-domains/embedded/03-object-pool-pattern.md +++ b/documents/en/vol8-domains/embedded/03-object-pool-pattern.md @@ -11,18 +11,18 @@ order: 3 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 8 +reading_time_minutes: 5 tags: - cpp-modern - intermediate - stm32f1 title: Object Pool Pattern translation: + engine: anthropic source: documents/vol8-domains/embedded/03-object-pool-pattern.md source_hash: 5ba90bc727848fabfbb3137a5b4c15371df735a928e8a72bddf7c2f81e87792f - translated_at: '2026-05-26T12:13:24.526475+00:00' - engine: anthropic token_count: 1211 + translated_at: '2026-05-26T12:13:24.526475+00:00' --- # Embedded C++ Tutorial: Object Pool Pattern diff --git a/documents/en/vol8-domains/embedded/03-uart/01-motivation-and-overview.md b/documents/en/vol8-domains/embedded/03-uart/01-motivation-and-overview.md index 251424ae3..c004e5973 100644 --- a/documents/en/vol8-domains/embedded/03-uart/01-motivation-and-overview.md +++ b/documents/en/vol8-domains/embedded/03-uart/01-motivation-and-overview.md @@ -1,20 +1,21 @@ --- -title: 'Part 31: From Buttons to Serial — Why UART Is the Foundation of Embedded Communication' -description: '' +chapter: 17 +difficulty: beginner +order: 1 +platform: stm32f1 +reading_time_minutes: 11 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 17 -order: 1 +title: 'Part 31: From Buttons to Serial — Why UART Is the Foundation of Embedded Communication' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/01-motivation-and-overview.md source_hash: efdeb2909faec61ce58456cb501ee25969620d8fd4bb5d49bb330384665ea915 - translated_at: '2026-05-26T12:14:49.931658+00:00' - engine: anthropic token_count: 1995 + translated_at: '2026-05-26T12:14:49.931658+00:00' +description: '' --- # Part 31: From Buttons to Serial — Why UART Is the Cornerstone of Embedded Communication diff --git a/documents/en/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md b/documents/en/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md index 4aeedb89b..f1fd1b0ad 100644 --- a/documents/en/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md +++ b/documents/en/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md @@ -1,20 +1,21 @@ --- -title: 'Part 32: UART Protocol In-Depth — How to Synchronize Without a Clock Line' -description: '' +chapter: 17 +difficulty: beginner +order: 2 +platform: stm32f1 +reading_time_minutes: 11 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 17 -order: 2 +title: 'Part 32: UART Protocol In-Depth — How to Synchronize Without a Clock Line' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md source_hash: b6d86655fcc8eff238a82143d6a1bc6a8bafa81f52bc43289df84bfda408f753 - translated_at: '2026-05-26T12:14:50.042005+00:00' - engine: anthropic token_count: 1530 + translated_at: '2026-05-26T12:14:50.042005+00:00' +description: '' --- # Part 32: UART Protocol In Depth — How to Synchronize Without a Clock Line diff --git a/documents/en/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md b/documents/en/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md index a23c85903..086a777cf 100644 --- a/documents/en/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md +++ b/documents/en/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md @@ -1,20 +1,21 @@ --- -title: 'Part 33: STM32 USART Peripheral — The Serial Engine Inside the Chip' -description: '' +chapter: 17 +difficulty: beginner +order: 3 +platform: stm32f1 +reading_time_minutes: 10 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 17 -order: 3 +title: 'Part 33: STM32 USART Peripheral — The Serial Engine Inside the Chip' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md source_hash: 04a873e68c1606743dd942721a7cea8eb0979698946fbce9259c1f1d13847968 - translated_at: '2026-05-26T12:15:16.059561+00:00' - engine: anthropic token_count: 1667 + translated_at: '2026-05-26T12:15:16.059561+00:00' +description: '' --- # Part 33: STM32 USART Peripheral — The Serial Engine Inside the Chip diff --git a/documents/en/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md b/documents/en/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md index 51d145721..646e450de 100644 --- a/documents/en/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md +++ b/documents/en/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md @@ -1,20 +1,21 @@ --- -title: 'Part 34: HAL UART Initialization and Transmission — Making the Chip Talk' -description: '' +chapter: 17 +difficulty: beginner +order: 4 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 17 -order: 4 +title: 'Part 34: HAL UART Initialization and Transmission — Making the Chip Talk' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md source_hash: 4418b714c7874cb00fde17309b315da325a619bbb39fd5be6aa890c1fb490118 - translated_at: '2026-05-26T12:15:55.985533+00:00' - engine: anthropic token_count: 1367 + translated_at: '2026-05-26T12:15:55.985533+00:00' +description: '' --- # Part 34: HAL UART Initialization and Transmission — Making the Chip Speak diff --git a/documents/en/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md b/documents/en/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md index 138d22494..556e77c23 100644 --- a/documents/en/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md +++ b/documents/en/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md @@ -1,21 +1,22 @@ --- -title: 'Part 35: `printf` Redirection and Blocking Receive — Making the Chip Speak - with `printf`, and Listen Too' -description: '' +chapter: 17 +difficulty: beginner +order: 5 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 17 -order: 5 +title: 'Part 35: `printf` Redirection and Blocking Receive — Making the Chip Speak + with `printf`, and Listen Too' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md source_hash: bfc84034896ca641ead87b2adc1105cac3d20bae8a305e95f3f2c0a7b13b2f2d - translated_at: '2026-05-26T12:15:52.556018+00:00' - engine: anthropic token_count: 1226 + translated_at: '2026-05-26T12:15:52.556018+00:00' +description: '' --- # Part 35: printf Retargeting and Blocking Receive — Making the Chip Speak with printf, and Learning to Listen diff --git a/documents/en/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md b/documents/en/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md index b3cc81141..30a9e27c2 100644 --- a/documents/en/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md +++ b/documents/en/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md @@ -1,21 +1,22 @@ --- -title: 'Part 36: Interrupt Basics and NVIC — Letting Hardware Proactively Notify the - CPU' -description: '' +chapter: 17 +difficulty: intermediate +order: 6 +platform: stm32f1 +reading_time_minutes: 9 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 6 +title: 'Part 36: Interrupt Basics and NVIC — Letting Hardware Proactively Notify the + CPU' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md source_hash: 827336d06a96eee8d7a8570ae26c012033296a4a714f004bf117b51af745d373 - translated_at: '2026-05-26T12:16:34.042121+00:00' - engine: anthropic token_count: 1323 + translated_at: '2026-05-26T12:16:34.042121+00:00' +description: '' --- # Part 36: Interrupt Basics and NVIC — Letting Hardware Notify the CPU Actively diff --git a/documents/en/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md b/documents/en/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md index 1dc4cf8dc..36a291201 100644 --- a/documents/en/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md +++ b/documents/en/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md @@ -1,21 +1,22 @@ --- -title: 'Part 37: Lock-Free Ring Buffer — A Safe Channel Between ISRs and the Main - Loop' -description: '' +chapter: 17 +difficulty: intermediate +order: 7 +platform: stm32f1 +reading_time_minutes: 10 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 7 +title: 'Part 37: Lock-Free Ring Buffer — A Safe Channel Between ISRs and the Main + Loop' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md source_hash: 8de84062a51802cb9a09dbbdaeff32ad8bcf8001044a2ed2a2ff603fd4841d6c - translated_at: '2026-05-26T12:17:01.081291+00:00' - engine: anthropic token_count: 1532 + translated_at: '2026-05-26T12:17:01.081291+00:00' +description: '' --- # Part 37: Lock-Free Ring Buffer — A Safe Channel Between ISR and Main Loop diff --git a/documents/en/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md b/documents/en/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md index 73632348a..a49e0c5a1 100644 --- a/documents/en/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md +++ b/documents/en/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md @@ -1,21 +1,22 @@ --- -title: 'Part 38: UART IRQ Handling and Callbacks — The Complete Puzzle of Interrupt - Reception' -description: '' +chapter: 17 +difficulty: intermediate +order: 8 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 8 +title: 'Part 38: UART IRQ Handling and Callbacks — The Complete Puzzle of Interrupt + Reception' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md source_hash: c0a86e9a6d69c3552d18c50ac4d5ac39eaa64a3903c3e233252fcb21b203d2a7 - translated_at: '2026-05-26T12:16:38.783352+00:00' - engine: anthropic token_count: 1360 + translated_at: '2026-05-26T12:16:38.783352+00:00' +description: '' --- # Part 38: UART IRQ Handling and Callbacks — The Complete Picture of Interrupt-Driven Reception diff --git a/documents/en/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md b/documents/en/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md index 377a70343..66a14fcfa 100644 --- a/documents/en/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md +++ b/documents/en/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md @@ -1,21 +1,22 @@ --- -title: 'Part 39: std::expected Error Handling — A Better Choice Than Exceptions in - Embedded Systems' -description: '' +chapter: 17 +difficulty: intermediate +order: 9 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 9 +title: 'Part 39: std::expected Error Handling — A Better Choice Than Exceptions in + Embedded Systems' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md source_hash: 3ceccc30709c1f1a003eb607da2588c224478548f7ff7a09157f2bec7ce526ec - translated_at: '2026-05-26T12:19:16.660521+00:00' - engine: anthropic token_count: 1380 + translated_at: '2026-05-26T12:19:16.660521+00:00' +description: '' --- # Part 39: `std::expected` Error Handling — A Better Choice Than Exceptions in Embedded diff --git a/documents/en/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md b/documents/en/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md index 5f9da25fa..5b4e19dc2 100644 --- a/documents/en/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md +++ b/documents/en/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md @@ -1,20 +1,21 @@ --- -title: 'Part 40: UART Driver Template — Zero-Size Abstraction and Compile-Time Dispatch' -description: '' +chapter: 17 +difficulty: intermediate +order: 10 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 10 +title: 'Part 40: UART Driver Template — Zero-Size Abstraction and Compile-Time Dispatch' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md source_hash: dcff20bb3e13302abb27dba51403a383f3dd3dd99b9a6bd6eb24732523c13192 - translated_at: '2026-05-26T12:17:42.925140+00:00' - engine: anthropic token_count: 1749 + translated_at: '2026-05-26T12:17:42.925140+00:00' +description: '' --- # Part 40: UART Driver Template — Zero-Size Abstraction and Compile-Time Dispatch diff --git a/documents/en/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md b/documents/en/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md index 3b40fc78b..e9e1603ae 100644 --- a/documents/en/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md +++ b/documents/en/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md @@ -1,21 +1,22 @@ --- -title: 'Part 41: Concepts-Constrained GPIO Initialization + UartManager — Type-Safe - Assembly' -description: '' +chapter: 17 +difficulty: intermediate +order: 11 +platform: stm32f1 +reading_time_minutes: 6 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 11 +title: 'Part 41: Concepts-Constrained GPIO Initialization + UartManager — Type-Safe + Assembly' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md source_hash: 4e378aa94dc28c0c50d94786d7dc29c0ecf48aa948e48195eaf0d0902ffd5fcf - translated_at: '2026-05-26T12:17:54.927783+00:00' - engine: anthropic token_count: 1340 + translated_at: '2026-05-26T12:17:54.927783+00:00' +description: '' --- # Part 41: Concepts-Constrained GPIO Initialization + UartManager — Type-Safe Assembly diff --git a/documents/en/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md b/documents/en/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md index eba40c3d1..ac71e8790 100644 --- a/documents/en/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md +++ b/documents/en/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md @@ -1,21 +1,22 @@ --- -title: 'Part 42: Command Processor and Full Code Walkthrough — From Serial Input to - LED Control' -description: '' +chapter: 17 +difficulty: intermediate +order: 12 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 12 +title: 'Part 42: Command Processor and Full Code Walkthrough — From Serial Input to + LED Control' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md source_hash: 45f223049477b1ca1c04f47775f0dbd144d88c025c01458534238c6d8009ad4d - translated_at: '2026-05-26T12:18:15.934277+00:00' - engine: anthropic token_count: 1789 + translated_at: '2026-05-26T12:18:15.934277+00:00' +description: '' --- # Part 42: Command Handler and Full Code Walkthrough — From Serial Input to LED Control diff --git a/documents/en/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md b/documents/en/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md index 588a85ec3..46e088380 100644 --- a/documents/en/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md +++ b/documents/en/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md @@ -1,20 +1,21 @@ --- -title: 'Part 43: Common Pitfalls and Practical Exercises — Getting Creative with UART' -description: '' +chapter: 17 +difficulty: intermediate +order: 13 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 17 -order: 13 +title: 'Part 43: Common Pitfalls and Practical Exercises — Getting Creative with UART' translation: + engine: anthropic source: documents/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md source_hash: 703f55d71c3658a109167fe2bcc8253f3cd6caebfb5b1c27ce00f0f3399c6bb0 - translated_at: '2026-05-26T12:18:34.119874+00:00' - engine: anthropic token_count: 1131 + translated_at: '2026-05-26T12:18:34.119874+00:00' +description: '' --- # Part 43: Common Pitfalls and Hands-on Exercises — Mastering UART diff --git a/documents/en/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md b/documents/en/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md index 3a642e020..9ce78ad05 100644 --- a/documents/en/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md +++ b/documents/en/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md @@ -11,18 +11,18 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 1: 构建工具链' -reading_time_minutes: 8 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 title: CRTP vs Runtime Polymorphism translation: + engine: anthropic source: documents/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md source_hash: 95df4c381cdb564a05ec206b9b503fd8220265fcfee7bd66011a07c6eb5a71c9 - translated_at: '2026-05-26T12:19:12.971255+00:00' - engine: anthropic token_count: 1038 + translated_at: '2026-05-26T12:19:12.971255+00:00' --- # Compile-Time Polymorphism vs. Runtime Polymorphism diff --git a/documents/en/vol8-domains/embedded/04-intrusive-containers.md b/documents/en/vol8-domains/embedded/04-intrusive-containers.md index 442eb5684..a13143eaa 100644 --- a/documents/en/vol8-domains/embedded/04-intrusive-containers.md +++ b/documents/en/vol8-domains/embedded/04-intrusive-containers.md @@ -11,18 +11,18 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 6: RAII与智能指针' -reading_time_minutes: 9 +reading_time_minutes: 7 tags: - cpp-modern - stm32f1 - intermediate title: Intrusive Container Design translation: + engine: anthropic source: documents/vol8-domains/embedded/04-intrusive-containers.md source_hash: be6a1adfb9f0ecf819e11505b29abc841596da95c16afb75d38001765af4d2f5 - translated_at: '2026-06-14T00:21:18.768776+00:00' - engine: anthropic token_count: 1425 + translated_at: '2026-06-14T00:21:18.768776+00:00' --- # Modern C++ for Embedded: Intrusive Container Design diff --git a/documents/en/vol8-domains/embedded/04-placement-new.md b/documents/en/vol8-domains/embedded/04-placement-new.md index 03156f0cb..d8ec571d7 100644 --- a/documents/en/vol8-domains/embedded/04-placement-new.md +++ b/documents/en/vol8-domains/embedded/04-placement-new.md @@ -11,18 +11,18 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 11 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 title: Using Placement New translation: + engine: anthropic source: documents/vol8-domains/embedded/04-placement-new.md source_hash: 25977c6db8c7cc7dc3e3caaec03250bb09393d7cd6df958d853d7b48c33c178a - translated_at: '2026-05-26T12:20:27.066187+00:00' - engine: anthropic token_count: 1817 + translated_at: '2026-05-26T12:20:27.066187+00:00' --- # Embedded C++ Tutorial: placement new diff --git a/documents/en/vol8-domains/embedded/05-interrupt-safe-coding.md b/documents/en/vol8-domains/embedded/05-interrupt-safe-coding.md index 9a5f50da2..2ab53c9ba 100644 --- a/documents/en/vol8-domains/embedded/05-interrupt-safe-coding.md +++ b/documents/en/vol8-domains/embedded/05-interrupt-safe-coding.md @@ -1,28 +1,28 @@ --- -title: Writing Interrupt-Safe Code -description: ISR-safe programming practices chapter: 10 -order: 5 -tags: -- cpp-modern -- intermediate -- stm32f1 -difficulty: advanced -reading_time_minutes: 25 -prerequisites: -- 'Chapter 10.1-10.4: 原子操作与内存序' cpp_standard: - 11 - 14 - 17 - 20 +description: ISR-safe programming practices +difficulty: advanced +order: 5 platform: stm32f1 +prerequisites: +- 'Chapter 10.1-10.4: 原子操作与内存序' +reading_time_minutes: 15 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: Writing Interrupt-Safe Code translation: + engine: anthropic source: documents/vol8-domains/embedded/05-interrupt-safe-coding.md source_hash: 95dfe114ab4697194a4ae0d40372ef7639ef7929f4c22f03d3414d5525bd98e4 - translated_at: '2026-05-26T12:23:39.677550+00:00' - engine: anthropic token_count: 3481 + translated_at: '2026-05-26T12:23:39.677550+00:00' --- # Modern C++ for Embedded Systems—Writing Interrupt-Safe Code diff --git a/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md b/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md index 24a9522e3..910b4377e 100644 --- a/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md +++ b/documents/en/vol8-domains/embedded/core-embedded-cpp-index.md @@ -1,59 +1,60 @@ --- -title: Table of Contents -description: '' +chapter: 0 +difficulty: intermediate +order: 0 +platform: stm32f1 +reading_time_minutes: 3 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 0 -order: 0 +title: Table of Contents translation: - source: documents/vol8-domains/embedded/core-embedded-cpp-index.md - source_hash: 72e8650253a4f675f6d4acacf91e083e3941857d300616c6228b1b73aef31e67 - translated_at: '2026-06-13T11:53:53.543475+00:00' engine: anthropic + source: documents/vol8-domains/embedded/core-embedded-cpp-index.md + source_hash: 43e930e53edd7d6ea7e44a71ba3dfb77736cff0ea3fdb04c87141b5e9e4c11d7 token_count: 858 + translated_at: '2026-06-15T09:31:47.242094+00:00' +description: '' --- # Table of Contents -This is the table of contents for "Modern C++ for Embedded Systems Tutorial". Click on a link to jump directly to the corresponding chapter. +This is the table of contents for "Modern C++ Tutorial for Embedded Systems". Click here to jump directly to the corresponding chapter. ## Chapter 0 - Preface and Fundamentals - [Preface](../../vol1-fundamentals/00-preface.md) - [Resource and Real-time Constraints in Embedded Systems](./01-resource-and-realtime-constraints.md) -- [C Language Crash Course](../../vol1-fundamentals/02-c-language-crash-course.md) +- [Crash Course on C Language](../../vol1-fundamentals/02-c-language-crash-course.md) - [C++98 Introduction: Namespaces, References, and Scope Resolution](../../vol1-fundamentals/03A-cpp98-namespace-reference.md) - [C++98 Function Interfaces: Overloading and Default Arguments](../../vol1-fundamentals/03B-cpp98-function-overload-default-args.md) - [C++98 OOP: Deep Dive into Classes and Objects](../../vol1-fundamentals/03C-cpp98-classes-and-objects.md) - [C++98 OOP: Inheritance and Polymorphism](../../vol1-fundamentals/03D-cpp98-inheritance-polymorphism.md) - [C++98 Operator Overloading](../../vol1-fundamentals/03E-cpp98-operator-overloading.md) - [C++98 Advanced: Type Casting, Dynamic Memory, and Exception Handling](../../vol1-fundamentals/03F-cpp98-casts-memory-exceptions.md) -- [When to Use C++ and Which Features (Compromises and Disabled Features)](../../vol1-fundamentals/04-when-to-use-cpp.md) -- [Language Selection Principles: The Real Trade-off Between Performance and Maintainability](../../vol1-fundamentals/05-language-choice-performance-vs-maintainability.md) +- [When to Use C++ and Which Features (Trade-offs and Disabled Features)](../../vol1-fundamentals/04-when-to-use-cpp.md) +- [Language Selection Principles: The Real Trade-off between Performance vs. Maintainability](../../vol1-fundamentals/05-language-choice-performance-vs-maintainability.md) - [Does C++ Necessarily Lead to Code Bloat?](../../vol6-performance/06-evaluating-performance-and-size.md) ## Chapter 1 - Build Toolchain - [A Casual Chat on Cross-compilation and a Simple CMake Guide](../../vol7-engineering/01-cross-compilation-and-cmake.md) - [Common Compiler Options Guide](../../vol7-engineering/02-compiler-options.md) -- [Linkers and Linker Scripts](../../vol7-engineering/03-linker-and-linker-scripts.md) +- [Linker and Linker Scripts](../../vol7-engineering/03-linker-and-linker-scripts.md) -## Chapter 2 - Zero-Overhead Abstraction +## Chapter 2 - Zero-Overhead Abstractions - [Zero-Overhead Abstraction](./01-zero-overhead-abstraction.md) - [Inlining and Compiler Optimization](../../vol6-performance/02-inline-and-compiler-optimization.md) -- [CRTP vs Runtime Polymorphism, Did You Know?](./04-crtp-vs-runtime-polymorphism.md) +- [CRTP vs. Runtime Polymorphism, Did You Know?](./04-crtp-vs-runtime-polymorphism.md) ## Chapter 3 - Memory and Object Management -- [Initializer Lists](../../vol3-standard-library/01-initializer-lists.md) +- [Initializer Lists](../../vol3-standard-library/11-initializer-lists.md) - [Empty Base Optimization (EBO)](../../vol4-advanced/03-empty-base-optimization.md) -- [Object Size and Trivial Types](../../vol3-standard-library/05-object-size-and-trivial-types.md) +- [Object Size and Trivial Types](../../vol3-standard-library/12-object-size-and-trivial-types.md) -## Chapter 4 - Compile-time Computation +## Chapter 4 - Compile-Time Computation - [if constexpr](../../vol4-advanced/vol3-metaprogramming-cpp20-23/index.md) @@ -67,12 +68,12 @@ This is the table of contents for "Modern C++ for Embedded Systems Tutorial". Cl ## Chapter 7 - Containers and Data Structures -- [array](../../vol3-standard-library/01-array.md) -- [span](../../vol3-standard-library/02-span.md) +- [array](../../vol3-standard-library/02-array.md) +- [span](../../vol3-standard-library/08-span.md) - [Circular Buffer](./03-circular-buffer.md) - [Intrusive Container Design](./04-intrusive-containers.md) - [ETL](./05-etl.md) -- [Custom Allocators](../../vol3-standard-library/06-custom-allocators.md) +- [Custom Allocators](../../vol3-standard-library/13-custom-allocators.md) ## Chapter 8 - Type Safety and Utility Types @@ -82,14 +83,14 @@ This is the table of contents for "Modern C++ for Embedded Systems Tutorial". Cl - [atomic](../../vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md) - [memory_order](../../vol5-concurrency/ch03-atomic-memory-model/02-memory-ordering.md) -- [Lock-free Data Structure Design](../../vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md) +- [Lock-Free Data Structure Design](../../vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md) - [mutex and RAII Guards](../../vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md) - [Writing Interrupt-Safe Code](./05-interrupt-safe-coding.md) - [Critical Section Protection Techniques](./05-interrupt-safe-coding.md) -## Chapter 11 - Modern C++ Feature Overview +## Chapter 11 - Modern C++ Features Overview -- [Three-way Comparison Operator](../../vol4-advanced/05-spaceship-operator.md) +- [Three-Way Comparison Operator](../../vol4-advanced/05-spaceship-operator.md) ## Chapter 12 - Template Fundamentals diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md index 8a6eadd8c..56e1215a2 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md @@ -1,34 +1,34 @@ --- -title: 'OnceCallback in Practice (Part 1): Motivation and Interface Design' +chapter: 1 +cpp_standard: +- 23 description: Starting from a real asynchronous callback bug, we break down the three major shortcomings of `std::function` in asynchronous scenarios, and design the complete target API for `OnceCallback`. -chapter: 1 -order: 1 -tags: -- host -- cpp-modern -- beginner -- 回调机制 -- 函数对象 difficulty: beginner +order: 1 platform: host -cpp_standard: -- 23 -reading_time_minutes: 11 prerequisites: - OnceCallback 前置知识(一):函数类型与模板偏特化 - OnceCallback 前置知识(五):std::move_only_function - OnceCallback 前置知识(六):Deducing this +reading_time_minutes: 10 related: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +title: 'OnceCallback in Practice (Part 1): Motivation and Interface Design' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md source_hash: b99ecb8050d929f2402dba605c6cfbedd93411dc4adebcb9f49ab7dca10337b9 - translated_at: '2026-05-26T12:24:43.140050+00:00' - engine: anthropic token_count: 1732 + translated_at: '2026-05-26T12:24:43.140050+00:00' --- # OnceCallback in Practice (Part 1): Motivation and Interface Design diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md index fb67fb5e3..5a8a041ff 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md @@ -1,37 +1,37 @@ --- -title: 'OnceCallback in Practice (Part 2): Building the Core Skeleton' +chapter: 1 +cpp_standard: +- 23 description: Building the OnceCallback class skeleton in five steps from scratch—template partial specialization, data members, constructor constraints, run() consumption semantics, and query interfaces -chapter: 1 -order: 2 -tags: -- host -- cpp-modern -- beginner -- 回调机制 -- 函数对象 -- 模板 difficulty: beginner +order: 2 platform: host -cpp_standard: -- 23 -reading_time_minutes: 13 prerequisites: - OnceCallback 实战(一):动机与接口设计 - OnceCallback 前置知识(一):函数类型与模板偏特化 - OnceCallback 前置知识(四):Concepts 与 requires 约束 - OnceCallback 前置知识(五):std::move_only_function - OnceCallback 前置知识(六):Deducing this +reading_time_minutes: 10 related: - OnceCallback 实战(三):bind_once 实现 - OnceCallback 实战(四):取消令牌设计 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: 'OnceCallback in Practice (Part 2): Building the Core Skeleton' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md source_hash: 537925a4f921a63c964f12d365b3e7c0d35a5abf8169998edb58cc011344d1ea - translated_at: '2026-05-26T12:25:22.536516+00:00' - engine: anthropic token_count: 2293 + translated_at: '2026-05-26T12:25:22.536516+00:00' --- # OnceCallback in Practice (Part 2): Building the Core Skeleton diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md index 826d84d97..f619a39c9 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md @@ -1,34 +1,34 @@ --- -title: 'OnceCallback in Practice (Part 3): Implementing `bind_once`' +chapter: 1 +cpp_standard: +- 23 description: A line-by-line breakdown of the parameter binding implementation in `bind_once`—from the motivation to lambda capture pack expansion, followed by manually walking through a complete template instantiation example. -chapter: 1 -order: 3 -tags: -- host -- cpp-modern -- beginner -- 回调机制 -- 函数对象 -- 模板 difficulty: beginner +order: 3 platform: host -cpp_standard: -- 23 -reading_time_minutes: 9 prerequisites: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 前置知识(二):std::invoke 与统一调用协议 - OnceCallback 前置知识(三):Lambda 高级特性 +reading_time_minutes: 7 related: - OnceCallback 实战(四):取消令牌设计 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: 'OnceCallback in Practice (Part 3): Implementing `bind_once`' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md source_hash: 4d4e48ce3f36e5b1346673c61f911113149c22e7d13b78b362a76a05497c0caa - translated_at: '2026-05-26T12:24:47.631061+00:00' - engine: anthropic token_count: 1481 + translated_at: '2026-05-26T12:24:47.631061+00:00' --- # OnceCallback in Practice (Part 3): Implementing bind_once diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md index eab3a452a..e1b69dd88 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md @@ -1,10 +1,20 @@ --- -title: 'OnceCallback in Practice (Part 4): Cancellation Token Design' +chapter: 1 +cpp_standard: +- 23 description: A deep dive into the design of CancelableToken — implementing a lightweight cancellation mechanism with `shared_ptr` + `atomic`, and how it integrates into the execution flow of `OnceCallback`. -chapter: 1 +difficulty: beginner order: 4 +platform: host +prerequisites: +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 +related: +- OnceCallback 实战(五):then 链式组合 +- OnceCallback 实战(六):测试与性能对比 tags: - host - cpp-modern @@ -13,23 +23,13 @@ tags: - atomic - 智能指针 - 引用计数 -difficulty: beginner -platform: host -cpp_standard: -- 23 -reading_time_minutes: 9 -prerequisites: -- OnceCallback 实战(二):核心骨架搭建 -- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 -related: -- OnceCallback 实战(五):then 链式组合 -- OnceCallback 实战(六):测试与性能对比 +title: 'OnceCallback in Practice (Part 4): Cancellation Token Design' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md source_hash: f81ee6ef5c051c052e0789a29f73b72a3087a5b7e64a5d769630dcdd45b48ea7 - translated_at: '2026-05-26T12:25:32.809695+00:00' - engine: anthropic token_count: 1525 + translated_at: '2026-05-26T12:25:32.809695+00:00' --- # OnceCallback in Practice (Part 4): Cancellation Token Design diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md index 4b5793510..2a4616dad 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md @@ -1,34 +1,34 @@ --- -title: 'OnceCallback in Practice (Part 5): Chaining with `then`' +chapter: 1 +cpp_standard: +- 23 description: A line-by-line breakdown of the ownership chain design in `then()` — from pipeline thinking to `void`/non-`void` branch handling, understanding the most elegant ownership management in `OnceCallback`. -chapter: 1 -order: 5 -tags: -- host -- cpp-modern -- beginner -- 回调机制 -- 函数对象 -- 模板 difficulty: beginner +order: 5 platform: host -cpp_standard: -- 23 -reading_time_minutes: 9 prerequisites: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 前置知识(二):std::invoke 与统一调用协议 - OnceCallback 前置知识(三):Lambda 高级特性 +reading_time_minutes: 8 related: - OnceCallback 实战(六):测试与性能对比 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: 'OnceCallback in Practice (Part 5): Chaining with `then`' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md source_hash: f5dd3bfba9d0e474bf30a0bb13f7b8a1d98951a7bef96793ec9db952dd6d2034 - translated_at: '2026-05-26T12:25:30.907930+00:00' - engine: anthropic token_count: 1629 + translated_at: '2026-05-26T12:25:30.907930+00:00' --- # OnceCallback in Practice (Part 5): Chaining with `then` diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md index 1111eefc6..af075640c 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md @@ -1,34 +1,34 @@ --- -title: 'OnceCallback in Practice (Part 6): Testing and Performance Comparison' +chapter: 1 +cpp_standard: +- 23 description: We systematically design six categories of test cases to verify all core behaviors of `OnceCallback`, and compare the performance differences against the original Chromium implementation and standard library solutions. -chapter: 1 -order: 6 -tags: -- host -- cpp-modern -- beginner -- 回调机制 -- 函数对象 difficulty: beginner +order: 6 platform: host -cpp_standard: -- 23 -reading_time_minutes: 10 prerequisites: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 实战(三):bind_once 实现 - OnceCallback 实战(四):取消令牌设计 - OnceCallback 实战(五):then 链式组合 +reading_time_minutes: 7 related: - OnceCallback 前置知识(五):std::move_only_function +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +title: 'OnceCallback in Practice (Part 6): Testing and Performance Comparison' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md source_hash: 2dfbbd169402f28789a1fe88fb11db240e59ec83f8a570b0880a99b0abb1b0bb - translated_at: '2026-06-13T11:54:42.854860+00:00' - engine: anthropic token_count: 1889 + translated_at: '2026-06-13T11:54:42.854860+00:00' --- # OnceCallback in Practice (Part 6): Testing and Performance Comparison diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md index c014a7a9a..b9bae9d58 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md @@ -1,36 +1,36 @@ --- -title: 'OnceCallback Prerequisites at a Glance: A Review of Core C++11/14/17 Features' -description: A quick review of all the fundamental C++ features required for the OnceCallback - series—move semantics, perfect forwarding, variadic templates, smart pointers, atomic - operations, lambda expressions, type traits, and more—preparing us for the deep - dive ahead. chapter: 0 -order: 0 -tags: -- host -- cpp-modern -- intermediate -- 基础 -- 入门 -difficulty: intermediate -platform: host cpp_standard: - 11 - 14 - 17 - 20 -reading_time_minutes: 25 +description: A quick review of all the fundamental C++ features required for the OnceCallback + series—move semantics, perfect forwarding, variadic templates, smart pointers, atomic + operations, lambda expressions, type traits, and more—preparing us for the deep + dive ahead. +difficulty: intermediate +order: 0 +platform: host prerequisites: - 卷一 C++ 基础入门 +reading_time_minutes: 14 related: - OnceCallback 前置知识(一):函数类型与模板偏特化 - OnceCallback 前置知识(三):Lambda 高级特性 +tags: +- host +- cpp-modern +- intermediate +- 基础 +- 入门 +title: 'OnceCallback Prerequisites at a Glance: A Review of Core C++11/14/17 Features' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md source_hash: 36ab223da7e08fe1f83ab09fe810ea3204c6f75675fbb239a06b2e33b445b543 - translated_at: '2026-05-26T12:26:53.872398+00:00' - engine: anthropic token_count: 2747 + translated_at: '2026-05-26T12:26:53.872398+00:00' --- # OnceCallback Prerequisite Quick Reference: Core C++11/14/17 Features Review diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md index 9a4d1ee9d..1030ec98a 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md @@ -1,35 +1,35 @@ --- -title: 'OnceCallback Prerequisites (Part 1): Function Types and Template Partial Specialization' -description: Gain a deep understanding of what the function type `int(int,int)` is, - along with the template partial specialization techniques behind `OnceCallback` - — how the compiler decomposes function signatures through pattern matching. chapter: 0 -order: 1 -tags: -- host -- cpp-modern -- intermediate -- 模板 -- 泛型 -difficulty: intermediate -platform: host cpp_standard: - 11 - 14 - 17 - 20 -reading_time_minutes: 10 +description: Gain a deep understanding of what the function type `int(int,int)` is, + along with the template partial specialization techniques behind `OnceCallback` + — how the compiler decomposes function signatures through pattern matching. +difficulty: intermediate +order: 1 +platform: host prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - OnceCallback 前置知识(五):std::move_only_function - OnceCallback 实战(二):核心骨架搭建 +tags: +- host +- cpp-modern +- intermediate +- 模板 +- 泛型 +title: 'OnceCallback Prerequisites (Part 1): Function Types and Template Partial Specialization' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md source_hash: c9b4c0d34cab59017adaa538c027529fb0e62ad5924b8f0d4f8f68c651e064e3 - translated_at: '2026-05-26T12:27:27.491366+00:00' - engine: anthropic token_count: 1509 + translated_at: '2026-05-26T12:27:27.491366+00:00' --- # Prerequisites for OnceCallback (Part 1): Function Types and Template Partial Specialization diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md index 4246a3c40..6b9f7b7df 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md @@ -1,33 +1,33 @@ --- -title: 'OnceCallback Prerequisites (Part 2): std::invoke and the Unified Call Protocol' +chapter: 0 +cpp_standard: +- 17 description: A deep dive into how `std::invoke` unifies the calling conventions of function pointers, member function pointers, lambda expressions, and functors, and the role of `std::invoke_result_t` in type deduction for `OnceCallback`. -chapter: 0 -order: 2 -tags: -- host -- cpp-modern -- intermediate -- 函数对象 -- std_invoke difficulty: intermediate +order: 2 platform: host -cpp_standard: -- 17 -reading_time_minutes: 10 prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 - OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 8 related: - OnceCallback 实战(三):bind_once 实现 - OnceCallback 实战(五):then 链式组合 +tags: +- host +- cpp-modern +- intermediate +- 函数对象 +- std_invoke +title: 'OnceCallback Prerequisites (Part 2): std::invoke and the Unified Call Protocol' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md source_hash: 138cb6dff4c2a4b0cbec4c16902d1d2a1375c8acf794d66bcd43ff239d6f55c0 - translated_at: '2026-05-26T12:27:42.281653+00:00' - engine: anthropic token_count: 1605 + translated_at: '2026-05-26T12:27:42.281653+00:00' --- # OnceCallback Prerequisites (Part 2): `std::invoke` and the Uniform Calling Convention diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md index 4d40c9142..7db8887d2 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md @@ -1,35 +1,35 @@ --- -title: 'OnceCallback Prerequisites (Part 3): Advanced Lambda Features' -description: An in-depth look at mutable lambdas, init captures, C++20 lambda capture - pack expansion, and generic lambdas — the core implementation techniques behind - `bind_once` and `then()` in `OnceCallback`. chapter: 0 -order: 3 -tags: -- host -- cpp-modern -- intermediate -- lambda -- 函数对象 -difficulty: intermediate -platform: host cpp_standard: - 14 - 17 - 20 - 23 -reading_time_minutes: 11 +description: An in-depth look at mutable lambdas, init captures, C++20 lambda capture + pack expansion, and generic lambdas — the core implementation techniques behind + `bind_once` and `then()` in `OnceCallback`. +difficulty: intermediate +order: 3 +platform: host prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - OnceCallback 实战(三):bind_once 实现 - OnceCallback 实战(五):then 链式组合 +tags: +- host +- cpp-modern +- intermediate +- lambda +- 函数对象 +title: 'OnceCallback Prerequisites (Part 3): Advanced Lambda Features' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md source_hash: 36cc54b68c7b78d692011d4a01cd95ad8df814c9f770cddfb22df259f52d7796 - translated_at: '2026-05-26T12:27:42.515153+00:00' - engine: anthropic token_count: 1886 + translated_at: '2026-05-26T12:27:42.515153+00:00' --- # Prerequisites for OnceCallback (Part 3): Advanced Lambda Features diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md index a76c4f692..da12e7a8a 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md @@ -1,33 +1,33 @@ --- -title: 'Prerequisites for OnceCallback (Part 4): Concepts and requires Constraints' +chapter: 0 +cpp_standard: +- 20 description: Starting from the real problem of template constructors hijacking move constructors, we explore how concepts and requires constraints protect the constructors of OnceCallback to ensure correct matching. -chapter: 0 -order: 4 -tags: -- host -- cpp-modern -- intermediate -- concepts -- 模板 difficulty: intermediate +order: 4 platform: host -cpp_standard: -- 20 -reading_time_minutes: 11 prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 - OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 9 related: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 前置知识(五):std::move_only_function +tags: +- host +- cpp-modern +- intermediate +- concepts +- 模板 +title: 'Prerequisites for OnceCallback (Part 4): Concepts and requires Constraints' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md source_hash: e8b1894804c419d1e2255cf1123bac98c561fa062ceb658827dd87fc35517e84 - translated_at: '2026-05-26T12:28:57.556303+00:00' - engine: anthropic token_count: 1757 + translated_at: '2026-05-26T12:28:57.556303+00:00' --- # Prerequisite Knowledge for OnceCallback (Part 4): Concepts and requires Constraints diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md index a3c4b3e40..ca61e4f8b 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md @@ -1,33 +1,33 @@ --- -title: 'Prerequisites for OnceCallback (Part 5): std::move_only_function (C++23)' +chapter: 0 +cpp_standard: +- 23 description: An in-depth look at C++23's std::move_only_function—the core storage type of OnceCallback—covering the evolution from std::function, SBO behavior, and why OnceCallback requires independent three-state management. -chapter: 0 -order: 5 -tags: -- host -- cpp-modern -- intermediate -- 函数对象 -- 智能指针 difficulty: intermediate +order: 5 platform: host -cpp_standard: -- 23 -reading_time_minutes: 10 prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 - OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 9 related: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 实战(六):测试与性能对比 +tags: +- host +- cpp-modern +- intermediate +- 函数对象 +- 智能指针 +title: 'Prerequisites for OnceCallback (Part 5): std::move_only_function (C++23)' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md source_hash: 61ad68cd231af8ec4dafcf8a45948cfa4ec4353ca184e11c04f06387fe805c6b - translated_at: '2026-05-26T12:29:11.875311+00:00' - engine: anthropic token_count: 1747 + translated_at: '2026-05-26T12:29:11.875311+00:00' --- # Prerequisites for OnceCallback (Part 5): std::move_only_function (C++23) diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md index 410cedd3b..b32ccd9e9 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md @@ -1,31 +1,31 @@ --- -title: 'OnceCallback Prerequisites (Part 6): Deducing this (C++23)' +chapter: 0 +cpp_standard: +- 23 description: A deep dive into how C++23 explicit object parameters (deducing this) allow `OnceCallback::run()` to elegantly intercept lvalue calls at compile time, replacing Chromium's double-overload hack. -chapter: 0 -order: 6 -tags: -- host -- cpp-modern -- intermediate -- 模板 difficulty: intermediate +order: 6 platform: host -cpp_standard: -- 23 -reading_time_minutes: 10 prerequisites: - OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - OnceCallback 实战(二):核心骨架搭建 - OnceCallback 前置知识(四):Concepts 与 requires 约束 +tags: +- host +- cpp-modern +- intermediate +- 模板 +title: 'OnceCallback Prerequisites (Part 6): Deducing this (C++23)' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md source_hash: d12fb3db3d69c7e5d8d830b169ac971f50fde46b1eb5255baed897fdfe1dbe18 - translated_at: '2026-05-26T12:28:53.615588+00:00' - engine: anthropic token_count: 1664 + translated_at: '2026-05-26T12:28:53.615588+00:00' --- # Prerequisite Knowledge for OnceCallback (Part 6): Deducing this (C++23) diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md index 3854a2283..7dae411fd 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md @@ -1,32 +1,32 @@ --- -title: 'once_callback Design Guide (Part 1): Motivation and Interface Design' +chapter: 1 +cpp_standard: +- 23 description: Starting from Chromium's OnceCallback, we design a C++23 move-only, single-fire callback component — Part one focuses on motivation analysis and API design. -chapter: 1 -order: 1 -tags: -- host -- cpp-modern -- advanced -- 回调机制 -- 函数对象 difficulty: advanced +order: 1 platform: host -cpp_standard: -- 23 -reading_time_minutes: 20 prerequisites: - std::function、std::invoke 与可调用对象 - 移动语义与完美转发 +reading_time_minutes: 18 related: - OnceCallback 与 RepeatingCallback - bind_once / bind_repeating 与参数绑定 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: 'once_callback Design Guide (Part 1): Motivation and Interface Design' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md source_hash: 5603d833c8de41566f86978b8ef04023a66df0cd757940ddccfe8a1b9a878601 - translated_at: '2026-05-26T12:31:28.247320+00:00' - engine: anthropic token_count: 3067 + translated_at: '2026-05-26T12:31:28.247320+00:00' --- # once_callback Design Guide (Part 1): Motivation and Interface Design diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md index c94fd3f43..c9c8bc753 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md @@ -1,32 +1,32 @@ --- -title: 'once_callback Design Guide (Part 2): Step-by-Step Implementation' +chapter: 1 +cpp_standard: +- 23 description: From the core skeleton to a complete component, a four-step walkthrough of the implementation strategy of `once_callback`, with a focus on understanding template techniques and ownership design. -chapter: 1 -order: 2 -tags: -- host -- cpp-modern -- advanced -- 回调机制 -- 函数对象 difficulty: advanced +order: 2 platform: host -cpp_standard: -- 23 -reading_time_minutes: 30 prerequisites: - once_callback 设计指南(一):动机与接口设计 +reading_time_minutes: 24 related: - bind_once / bind_repeating 与参数绑定 - 回调取消与组合模式 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: 'once_callback Design Guide (Part 2): Step-by-Step Implementation' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md source_hash: bdd1a944cddf8dc214171648a079e8a32b7c01d4f58dda52c516ade3096ef21a - translated_at: '2026-05-26T12:31:28.959391+00:00' - engine: anthropic token_count: 4335 + translated_at: '2026-05-26T12:31:28.959391+00:00' --- # once_callback Design Guide (Part 2): Step-by-Step Implementation diff --git a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md index 3f76a0e09..06aeb1fa1 100644 --- a/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md +++ b/documents/en/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md @@ -1,32 +1,32 @@ --- -title: 'once_callback Design Guide (Part 3): Testing Strategy and Performance Comparison' +chapter: 1 +cpp_standard: +- 23 description: Design system test cases for `once_callback`, compare performance differences with the original Chromium version and the standard library approach, and summarize the design trade-offs. -chapter: 1 -order: 3 -tags: -- host -- cpp-modern -- advanced -- 回调机制 -- 函数对象 difficulty: advanced +order: 3 platform: host -cpp_standard: -- 23 -reading_time_minutes: 13 prerequisites: - once_callback 设计指南(一):动机与接口设计 - once_callback 设计指南(二):逐步实现 +reading_time_minutes: 11 related: - 回调取消与组合模式 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: 'once_callback Design Guide (Part 3): Testing Strategy and Performance Comparison' translation: + engine: anthropic source: documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md source_hash: 25b2e6cda1efd104125afd610167dda7f5b19ee3cf346fb3ccf3b83bf83c8a54 - translated_at: '2026-06-13T11:55:17.638057+00:00' - engine: anthropic token_count: 2581 + translated_at: '2026-06-13T11:55:17.638057+00:00' --- # once_callback Design Guide (Part 3): Testing Strategy and Performance Comparison diff --git a/documents/vol1-fundamentals/02-c-language-crash-course.md b/documents/vol1-fundamentals/02-c-language-crash-course.md index 7a19ea66f..4cbd36edf 100644 --- a/documents/vol1-fundamentals/02-c-language-crash-course.md +++ b/documents/vol1-fundamentals/02-c-language-crash-course.md @@ -10,7 +10,7 @@ difficulty: beginner order: 2 platform: host prerequisites: [] -reading_time_minutes: 40 +reading_time_minutes: 27 related: [] tags: - cpp-modern diff --git a/documents/vol1-fundamentals/03A-cpp98-namespace-reference.md b/documents/vol1-fundamentals/03A-cpp98-namespace-reference.md index 30097c148..03a80a991 100644 --- a/documents/vol1-fundamentals/03A-cpp98-namespace-reference.md +++ b/documents/vol1-fundamentals/03A-cpp98-namespace-reference.md @@ -1,24 +1,27 @@ --- -title: "C++98入门:命名空间、引用与作用域解析" -description: "从C到C++的第一步——命名空间解决名称冲突、引用替代指针传参、作用域解析访问全局与命名空间成员,三大基础特性彻底讲透" chapter: 0 -order: 3 -tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从C到C++的第一步——命名空间解决名称冲突、引用替代指针传参、作用域解析访问全局与命名空间成员,三大基础特性彻底讲透 difficulty: beginner -reading_time_minutes: 20 +order: 3 +platform: host prerequisites: - - "C语言速通复习" +- C语言速通复习 +reading_time_minutes: 16 related: - - "C++98函数接口:重载与默认参数" -cpp_standard: [11, 14, 17, 20] -platform: host +- C++98函数接口:重载与默认参数 +tags: +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: C++98入门:命名空间、引用与作用域解析 --- - # C++98入门:命名空间、引用与作用域解析 > 完整的仓库地址在 [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP) 中,您也可以光顾一下,喜欢的话给一个 Star 激励一下作者 diff --git a/documents/vol1-fundamentals/03B-cpp98-function-overload-default-args.md b/documents/vol1-fundamentals/03B-cpp98-function-overload-default-args.md index a6fc63a7f..7a10f7a26 100644 --- a/documents/vol1-fundamentals/03B-cpp98-function-overload-default-args.md +++ b/documents/vol1-fundamentals/03B-cpp98-function-overload-default-args.md @@ -11,7 +11,7 @@ order: 3 platform: host prerequisites: - C++98入门:命名空间、引用与作用域解析 -reading_time_minutes: 16 +reading_time_minutes: 14 related: - C++98面向对象:类与对象深度剖析 tags: diff --git a/documents/vol1-fundamentals/03C-cpp98-classes-and-objects.md b/documents/vol1-fundamentals/03C-cpp98-classes-and-objects.md index 86747c1ce..f2eebe5e0 100644 --- a/documents/vol1-fundamentals/03C-cpp98-classes-and-objects.md +++ b/documents/vol1-fundamentals/03C-cpp98-classes-and-objects.md @@ -1,26 +1,29 @@ --- -title: "C++98面向对象:类与对象深度剖析" -description: "从C结构体到C++类的跨越——访问控制、构造析构、初始化列表、this指针、静态成员、const成员函数、友元、explicit和mutable,讲清楚每一个细节" chapter: 0 -order: 3 -tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从C结构体到C++类的跨越——访问控制、构造析构、初始化列表、this指针、静态成员、const成员函数、友元、explicit和mutable,讲清楚每一个细节 difficulty: beginner -reading_time_minutes: 30 +order: 3 +platform: host prerequisites: - - "C++98入门:命名空间、引用与作用域解析" - - "C++98函数接口:重载与默认参数" +- C++98入门:命名空间、引用与作用域解析 +- C++98函数接口:重载与默认参数 +reading_time_minutes: 23 related: - - "C++98面向对象:继承与多态" - - "C++98运算符重载" -cpp_standard: [11, 14, 17, 20] -platform: host +- C++98面向对象:继承与多态 +- C++98运算符重载 +tags: +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: C++98面向对象:类与对象深度剖析 --- - # C++98面向对象:类与对象深度剖析 > 完整的仓库地址在 [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP) 中,您也可以光顾一下,喜欢的话给一个 Star 激励一下作者 diff --git a/documents/vol1-fundamentals/03D-cpp98-inheritance-polymorphism.md b/documents/vol1-fundamentals/03D-cpp98-inheritance-polymorphism.md index f8bc192c1..c6045e3a5 100644 --- a/documents/vol1-fundamentals/03D-cpp98-inheritance-polymorphism.md +++ b/documents/vol1-fundamentals/03D-cpp98-inheritance-polymorphism.md @@ -11,7 +11,7 @@ order: 3 platform: host prerequisites: - C++98面向对象:类与对象深度剖析 -reading_time_minutes: 20 +reading_time_minutes: 16 related: - C++98运算符重载 - 何时用C++、用哪些C++特性 diff --git a/documents/vol1-fundamentals/03E-cpp98-operator-overloading.md b/documents/vol1-fundamentals/03E-cpp98-operator-overloading.md index 272093d87..466434bcd 100644 --- a/documents/vol1-fundamentals/03E-cpp98-operator-overloading.md +++ b/documents/vol1-fundamentals/03E-cpp98-operator-overloading.md @@ -11,7 +11,7 @@ order: 3 platform: host prerequisites: - C++98面向对象:类与对象深度剖析 -reading_time_minutes: 13 +reading_time_minutes: 10 related: - C++98面向对象:继承与多态 - C++98进阶:类型转换、动态内存与异常处理 diff --git a/documents/vol1-fundamentals/03F-cpp98-casts-memory-exceptions.md b/documents/vol1-fundamentals/03F-cpp98-casts-memory-exceptions.md index cf00d2c1f..cfb3fc09c 100644 --- a/documents/vol1-fundamentals/03F-cpp98-casts-memory-exceptions.md +++ b/documents/vol1-fundamentals/03F-cpp98-casts-memory-exceptions.md @@ -1,24 +1,27 @@ --- -title: "C++98进阶:类型转换、动态内存与异常处理" -description: "四种C++类型转换运算符的精确使用场景、new/delete与placement new管理动态对象、异常处理机制与嵌入式取舍、inline和typedef" chapter: 0 -order: 3 -tags: - - cpp-modern - - host - - intermediate - - 进阶 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 四种C++类型转换运算符的精确使用场景、new/delete与placement new管理动态对象、异常处理机制与嵌入式取舍、inline和typedef difficulty: intermediate -reading_time_minutes: 25 +order: 3 +platform: host prerequisites: - - "C++98面向对象:类与对象深度剖析" - - "C++98面向对象:继承与多态" +- C++98面向对象:类与对象深度剖析 +- C++98面向对象:继承与多态 +reading_time_minutes: 19 related: - - "何时用C++、用哪些C++特性" -cpp_standard: [11, 14, 17, 20] -platform: host +- 何时用C++、用哪些C++特性 +tags: +- cpp-modern +- host +- intermediate +- 进阶 +title: C++98进阶:类型转换、动态内存与异常处理 --- - # C++98进阶:类型转换、动态内存与异常处理 > 完整的仓库地址在 [Tutorial_AwesomeModernCPP](https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeModernCPP) 中,您也可以光顾一下,喜欢的话给一个 Star 激励一下作者 diff --git a/documents/vol1-fundamentals/04-when-to-use-cpp.md b/documents/vol1-fundamentals/04-when-to-use-cpp.md index 1ece5c6b8..426031279 100644 --- a/documents/vol1-fundamentals/04-when-to-use-cpp.md +++ b/documents/vol1-fundamentals/04-when-to-use-cpp.md @@ -1,22 +1,25 @@ --- -title: "何时用C++、用哪些C++特性" -description: "探讨何时选择C++而非C,以及如何在嵌入式环境中明智地使用C++特性,包括推荐使用、折中和禁用的特性" chapter: 0 -order: 4 -tags: - - host - - cpp-modern - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 探讨何时选择C++而非C,以及如何在嵌入式环境中明智地使用C++特性,包括推荐使用、折中和禁用的特性 difficulty: beginner -reading_time_minutes: 18 +order: 4 +platform: host prerequisites: [] +reading_time_minutes: 16 related: [] -cpp_standard: [11, 14, 17, 20] -platform: host +tags: +- host +- cpp-modern +- beginner +- 入门 +- 基础 +title: 何时用C++、用哪些C++特性 --- - # 何时用 C++、用哪些 C++ 特性 说实话,每次看到嵌入式圈子里爆发"C vs C++"的圣战,笔者都觉得挺无奈的。争论往往很快就滑向信仰层面——用 C 的觉得 C++ 是邪教,用 C++ 的觉得 C 是原始社会。但真正的问题是:我们手头的这个项目,在这块硬件上,用这门语言划不划算?这个问题没有任何人能替你回答,但我可以分享一下在实际项目中踩出来的经验,帮大家少走点弯路。 diff --git a/documents/vol1-fundamentals/c_tutorials/01-program-structure-and-compilation.md b/documents/vol1-fundamentals/c_tutorials/01-program-structure-and-compilation.md index d33aa21f3..c6cb196b3 100644 --- a/documents/vol1-fundamentals/c_tutorials/01-program-structure-and-compilation.md +++ b/documents/vol1-fundamentals/c_tutorials/01-program-structure-and-compilation.md @@ -1,21 +1,21 @@ --- -title: "程序结构与编译基础" -description: "理解 C 程序的基本结构、编译四阶段流程、头文件机制和基本 I/O,为后续 C++ 学习打下编译模型基础" chapter: 1 -order: 1 -tags: - - host - - cpp-modern - - beginner - - 入门 +cpp_standard: +- 11 +description: 理解 C 程序的基本结构、编译四阶段流程、头文件机制和基本 I/O,为后续 C++ 学习打下编译模型基础 difficulty: beginner +order: 1 platform: host -reading_time_minutes: 20 -cpp_standard: [11] prerequisites: - - "无(本系列第一篇)" +- 无(本系列第一篇) +reading_time_minutes: 13 +tags: +- host +- cpp-modern +- beginner +- 入门 +title: 程序结构与编译基础 --- - # 程序结构与编译基础 如果你之前写过一些 C 代码,大概率是在 IDE 里点一下"运行"就完事了——代码怎么从 `.c` 文件变成一个能跑的二进制,这个中间过程可能从来没关心过。但说实话,理解编译模型这件事,在后续学习 C++ 的时候会变得非常关键:模板实例化、头文件策略、ODR(One Definition Rule)这些东西,如果不懂编译的基本流程,基本上就是在黑箱操作。所以我们从一开始就把这件事理清楚。 diff --git a/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md b/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md index 04044b954..a868de482 100644 --- a/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md +++ b/documents/vol1-fundamentals/c_tutorials/02A-data-types-basics.md @@ -1,22 +1,22 @@ --- -title: "数据类型基础:整数与内存" -description: "从零开始理解 C 语言的整型家族、有符号与无符号的区别、固定宽度类型和 sizeof 运算符,为后续学习打下类型系统基础" chapter: 1 -order: 2 -tags: - - host - - cpp-modern - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +description: 从零开始理解 C 语言的整型家族、有符号与无符号的区别、固定宽度类型和 sizeof 运算符,为后续学习打下类型系统基础 difficulty: beginner +order: 2 platform: host -reading_time_minutes: 15 -cpp_standard: [11] prerequisites: - - "程序结构与编译基础" +- 程序结构与编译基础 +reading_time_minutes: 12 +tags: +- host +- cpp-modern +- beginner +- 入门 +- 基础 +title: 数据类型基础:整数与内存 --- - # 数据类型基础:整数与内存 如果你之前接触过 Python,可能会记得写 `x = 42` 就完事了——不用告诉 Python 这个 `x` 是整数还是小数,解释器自己猜。但到了 C 这边,规矩就变了:每一个变量在出生的时候,我们必须明确告诉编译器"这家伙到底是什么类型的"。乍一看像是多此一举,但实际上这个"声明类型"的动作,是 C 语言性能强大的根基——编译器因为知道每个变量占多少内存、数据怎么存储,才能生成最高效的机器码。 diff --git a/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md b/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md index 20298863e..a089d145a 100644 --- a/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md +++ b/documents/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md @@ -1,22 +1,22 @@ --- -title: "浮点、字符、const 与类型转换" -description: "掌握 C 语言的浮点类型与精度问题、字符存储与编码、const 限定符和隐式类型转换规则,理解 C++ 类型安全设计的动机" chapter: 1 -order: 3 -tags: - - host - - cpp-modern - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +description: 掌握 C 语言的浮点类型与精度问题、字符存储与编码、const 限定符和隐式类型转换规则,理解 C++ 类型安全设计的动机 difficulty: beginner +order: 3 platform: host -reading_time_minutes: 15 -cpp_standard: [11] prerequisites: - - "数据类型基础:整数与内存" +- 数据类型基础:整数与内存 +reading_time_minutes: 12 +tags: +- host +- cpp-modern +- beginner +- 入门 +- 基础 +title: 浮点、字符、const 与类型转换 --- - # 浮点、字符、const 与类型转换 上一篇里我们把整数家族从里到外拆了一遍——整型层级、有符号无符号、固定宽度类型和 sizeof。但程序世界里不只有整数:商品价格需要小数,屏幕上的文字需要字符,变量声明后有时候需要保护它不被乱改,不同类型的数据混在一起运算时编译器到底怎么处理。这些就是我们今天要一块一块啃的内容。 diff --git a/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md b/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md index c9043b5b4..3cd2b0f50 100644 --- a/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md +++ b/documents/vol1-fundamentals/c_tutorials/03A-operators-basics.md @@ -8,7 +8,7 @@ order: 4 platform: host prerequisites: - 浮点、字符、const 与类型转换 -reading_time_minutes: 11 +reading_time_minutes: 9 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md b/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md index 116026fb5..bc794ab94 100644 --- a/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md +++ b/documents/vol1-fundamentals/c_tutorials/03B-bitwise-and-evaluation.md @@ -1,21 +1,21 @@ --- -title: "位运算与求值顺序" -description: "深入位运算的四大操作、移位注意事项、运算符优先级陷阱、求值顺序与序列点,理解未定义行为的本质" chapter: 1 -order: 5 -tags: - - host - - cpp-modern - - beginner - - 入门 +cpp_standard: +- 11 +description: 深入位运算的四大操作、移位注意事项、运算符优先级陷阱、求值顺序与序列点,理解未定义行为的本质 difficulty: beginner +order: 5 platform: host -reading_time_minutes: 15 -cpp_standard: [11] prerequisites: - - "运算符基础:让数据动起来" +- 运算符基础:让数据动起来 +reading_time_minutes: 10 +tags: +- host +- cpp-modern +- beginner +- 入门 +title: 位运算与求值顺序 --- - # 位运算与求值顺序 上一篇里我们把算术、关系、逻辑这些常用运算符过了一遍。现在我们来啃两块比较硬的骨头:位运算和求值顺序。位运算在一般的应用层编程中用得不多,但如果你以后要接触嵌入式开发或者底层系统编程,位运算就是你的日常工具——配置硬件寄存器、解析通信协议的位字段、实现标志位集合,全靠它。求值顺序和序列点则是理解"为什么有些代码在不同编译器上结果不一样"的关键。 diff --git a/documents/vol1-fundamentals/c_tutorials/04-control-flow.md b/documents/vol1-fundamentals/c_tutorials/04-control-flow.md index a8a236af6..21823228f 100644 --- a/documents/vol1-fundamentals/c_tutorials/04-control-flow.md +++ b/documents/vol1-fundamentals/c_tutorials/04-control-flow.md @@ -8,7 +8,7 @@ order: 6 platform: host prerequisites: - 位运算与求值顺序 -reading_time_minutes: 15 +reading_time_minutes: 11 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/05-function-basics.md b/documents/vol1-fundamentals/c_tutorials/05-function-basics.md index 527b288f4..5de6cd745 100644 --- a/documents/vol1-fundamentals/c_tutorials/05-function-basics.md +++ b/documents/vol1-fundamentals/c_tutorials/05-function-basics.md @@ -8,7 +8,7 @@ order: 7 platform: host prerequisites: - 指针与数组、const 和空指针 -reading_time_minutes: 12 +reading_time_minutes: 10 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md b/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md index 42558ad14..568b3e23b 100644 --- a/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md +++ b/documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md @@ -8,7 +8,7 @@ order: 8 platform: host prerequisites: - 控制流:让程序学会选择和重复 -reading_time_minutes: 24 +reading_time_minutes: 20 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md b/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md index e3d1e00f9..0481d0123 100644 --- a/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md +++ b/documents/vol1-fundamentals/c_tutorials/07A-pointer-essentials.md @@ -9,7 +9,7 @@ platform: host prerequisites: - 数据类型基础:整数与内存 - 运算符基础:让数据动起来 -reading_time_minutes: 12 +reading_time_minutes: 10 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md b/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md index db8eddd9e..f2a81414f 100644 --- a/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md +++ b/documents/vol1-fundamentals/c_tutorials/07B-pointers-arrays-const.md @@ -1,21 +1,21 @@ --- -title: "指针与数组、const 和空指针" -description: "深入理解数组名退化为指针的机制、const 与指针的四种组合、NULL 指针和野指针的防范,为学习 C++ 引用和智能指针打下基础" chapter: 1 -order: 10 -tags: - - host - - cpp-modern - - beginner - - 入门 +cpp_standard: +- 11 +description: 深入理解数组名退化为指针的机制、const 与指针的四种组合、NULL 指针和野指针的防范,为学习 C++ 引用和智能指针打下基础 difficulty: beginner +order: 10 platform: host -reading_time_minutes: 15 -cpp_standard: [11] prerequisites: - - "指针入门:地址的世界" +- 指针入门:地址的世界 +reading_time_minutes: 11 +tags: +- host +- cpp-modern +- beginner +- 入门 +title: 指针与数组、const 和空指针 --- - # 指针与数组、const 和空指针 上一篇里我们掌握了指针的基本操作——声明、初始化、取地址、解引用、指针运算。现在我们来啃指针里几个比较绕但非常重要的应用:数组和指针之间到底是什么关系,`const` 和指针组合在一起有多少种意思,以及 NULL 指针和野指针为什么这么危险。 diff --git a/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md b/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md index c305b6f60..40501191d 100644 --- a/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md +++ b/documents/vol1-fundamentals/c_tutorials/08A-multi-level-pointers.md @@ -8,7 +8,7 @@ order: 11 platform: host prerequisites: - 指针与数组、const 和空指针 -reading_time_minutes: 12 +reading_time_minutes: 9 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md b/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md index 9dcdfd33a..d2f89f9f0 100644 --- a/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md +++ b/documents/vol1-fundamentals/c_tutorials/08B-restrict-incomplete-types.md @@ -9,7 +9,7 @@ order: 12 platform: host prerequisites: - 多级指针与声明读法 -reading_time_minutes: 12 +reading_time_minutes: 9 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/09-function-pointers-and-callbacks.md b/documents/vol1-fundamentals/c_tutorials/09-function-pointers-and-callbacks.md index a70df13f2..45fb4dd46 100644 --- a/documents/vol1-fundamentals/c_tutorials/09-function-pointers-and-callbacks.md +++ b/documents/vol1-fundamentals/c_tutorials/09-function-pointers-and-callbacks.md @@ -10,7 +10,7 @@ prerequisites: - 07A 指针基础与核心用法 - 07B 指针、数组与 const - 08A 多级指针与函数参数 -reading_time_minutes: 13 +reading_time_minutes: 10 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/10-arrays-deep-dive.md b/documents/vol1-fundamentals/c_tutorials/10-arrays-deep-dive.md index e4df7f2c0..1e9a26abd 100644 --- a/documents/vol1-fundamentals/c_tutorials/10-arrays-deep-dive.md +++ b/documents/vol1-fundamentals/c_tutorials/10-arrays-deep-dive.md @@ -8,7 +8,7 @@ order: 14 platform: host prerequisites: - 指针与数组、const 和空指针 -reading_time_minutes: 23 +reading_time_minutes: 18 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/11-c-strings-and-buffer-safety.md b/documents/vol1-fundamentals/c_tutorials/11-c-strings-and-buffer-safety.md index 105e5262d..30969ff4e 100644 --- a/documents/vol1-fundamentals/c_tutorials/11-c-strings-and-buffer-safety.md +++ b/documents/vol1-fundamentals/c_tutorials/11-c-strings-and-buffer-safety.md @@ -1,22 +1,23 @@ --- -title: "C 字符串与缓冲区安全" -description: "理解 C 字符串 \\0 终止的内存模型,掌握 string.h 核心函数和 snprintf 安全格式化,识别并防范缓冲区溢出漏洞" chapter: 1 -order: 15 -tags: - - host - - cpp-modern - - beginner - - 入门 - - 基础 +cpp_standard: +- 11 +- 17 +description: 理解 C 字符串 \0 终止的内存模型,掌握 string.h 核心函数和 snprintf 安全格式化,识别并防范缓冲区溢出漏洞 difficulty: beginner +order: 15 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 17] prerequisites: - - "指针与数组、const 和空指针" +- 指针与数组、const 和空指针 +reading_time_minutes: 13 +tags: +- host +- cpp-modern +- beginner +- 入门 +- 基础 +title: C 字符串与缓冲区安全 --- - # C 字符串与缓冲区安全 C 语言里没有真正的"字符串类型"——这是每一个从 C 转向 C++ 的开发者都会发出的感叹。在 C 的世界里,字符串就是一块以 `\0` 结尾的 `char` 数组,所有的操作都建立在这个约定之上。这个约定简单到令人感动,也脆弱到令人崩溃——你忘记写那个 `\0`,整个程序的行为就是未定义的;你把一个 100 字节的字符串拷进 50 字节的缓冲区,缓冲区后面的内存就被你踩烂了。 diff --git a/documents/vol1-fundamentals/c_tutorials/12-struct-and-memory-alignment.md b/documents/vol1-fundamentals/c_tutorials/12-struct-and-memory-alignment.md index 9cd856025..7a4a4b9a1 100644 --- a/documents/vol1-fundamentals/c_tutorials/12-struct-and-memory-alignment.md +++ b/documents/vol1-fundamentals/c_tutorials/12-struct-and-memory-alignment.md @@ -8,7 +8,7 @@ order: 16 platform: host prerequisites: - restrict、不完整类型与结构体指针 -reading_time_minutes: 24 +reading_time_minutes: 20 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/13-union-enum-bitfield-typedef.md b/documents/vol1-fundamentals/c_tutorials/13-union-enum-bitfield-typedef.md index db825499e..59929fff5 100644 --- a/documents/vol1-fundamentals/c_tutorials/13-union-enum-bitfield-typedef.md +++ b/documents/vol1-fundamentals/c_tutorials/13-union-enum-bitfield-typedef.md @@ -10,7 +10,7 @@ order: 17 platform: host prerequisites: - 12 结构体与内存对齐 -reading_time_minutes: 15 +reading_time_minutes: 11 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/14-dynamic-memory.md b/documents/vol1-fundamentals/c_tutorials/14-dynamic-memory.md index 97e6bfc81..44d2ac11d 100644 --- a/documents/vol1-fundamentals/c_tutorials/14-dynamic-memory.md +++ b/documents/vol1-fundamentals/c_tutorials/14-dynamic-memory.md @@ -11,7 +11,7 @@ order: 18 platform: host prerequisites: - 结构体与内存对齐 -reading_time_minutes: 10 +reading_time_minutes: 9 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/15-preprocessor-and-multifile.md b/documents/vol1-fundamentals/c_tutorials/15-preprocessor-and-multifile.md index 22baa11f0..745e30220 100644 --- a/documents/vol1-fundamentals/c_tutorials/15-preprocessor-and-multifile.md +++ b/documents/vol1-fundamentals/c_tutorials/15-preprocessor-and-multifile.md @@ -11,7 +11,7 @@ order: 19 platform: host prerequisites: - 动态内存管理 -reading_time_minutes: 7 +reading_time_minutes: 6 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/16-file-io-and-stdlib.md b/documents/vol1-fundamentals/c_tutorials/16-file-io-and-stdlib.md index f1fb2f218..f7c3a07d7 100644 --- a/documents/vol1-fundamentals/c_tutorials/16-file-io-and-stdlib.md +++ b/documents/vol1-fundamentals/c_tutorials/16-file-io-and-stdlib.md @@ -12,7 +12,7 @@ prerequisites: - 11 C 字符串与缓冲区安全 - 12 结构体与内存对齐 - 14 动态内存管理 -reading_time_minutes: 11 +reading_time_minutes: 9 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/02-cache-and-memory-hierarchy.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/02-cache-and-memory-hierarchy.md index 6bc85c81b..6d988e610 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/02-cache-and-memory-hierarchy.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/02-cache-and-memory-hierarchy.md @@ -1,24 +1,25 @@ --- -title: "Cache 机制与内存层次" -description: "从内存层次结构出发,拆解缓存行、映射策略、MESI 一致性协议的工作机制,落到缓存友好编程实践和 C++ 的缓存行对齐工具" chapter: 1 -order: 102 -tags: - - host - - cpp-modern - - intermediate - - 优化 - - 内存管理 +cpp_standard: +- 11 +- 17 +description: 从内存层次结构出发,拆解缓存行、映射策略、MESI 一致性协议的工作机制,落到缓存友好编程实践和 C++ 的缓存行对齐工具 difficulty: intermediate +order: 102 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 17] prerequisites: - - "数据类型基础:整数与内存" - - "指针与数组" - - "结构体与内存布局" +- 数据类型基础:整数与内存 +- 指针与数组 +- 结构体与内存布局 +reading_time_minutes: 20 +tags: +- host +- cpp-modern +- intermediate +- 优化 +- 内存管理 +title: Cache 机制与内存层次 --- - # Cache 机制与内存层次 如果你的程序跑得慢,而你已经在算法层面把时间复杂度压到了极限,那瓶颈很可能不是 CPU 算不过来,而是它在那儿干等着数据从内存搬过来。现代 CPU 的运算速度和主存的访问速度之间差了几个数量级——如果不在这条鸿沟上搭几座桥,再强的运算单元也只能望洋兴叹。这些"桥"就是我们今天要聊的主角:Cache。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/03-c-traps-and-pitfalls.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/03-c-traps-and-pitfalls.md index 6a1b6b91f..13ce23428 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/03-c-traps-and-pitfalls.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/03-c-traps-and-pitfalls.md @@ -1,24 +1,26 @@ --- -title: "C 语言陷阱与常见错误" -description: "系统梳理 C 语言中最容易踩的语法与语义陷阱,从编译器行为和标准规范的角度搞清楚为什么会出错,以及 C++ 做了哪些改进" chapter: 1 -order: 19 -tags: - - host - - cpp-modern - - intermediate - - 进阶 - - 基础 +cpp_standard: +- 11 +- 14 +- 17 +description: 系统梳理 C 语言中最容易踩的语法与语义陷阱,从编译器行为和标准规范的角度搞清楚为什么会出错,以及 C++ 做了哪些改进 difficulty: intermediate +order: 19 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17] prerequisites: - - "数据类型基础:整数与内存" - - "运算符与表达式基础" - - "控制流:条件与循环" +- 数据类型基础:整数与内存 +- 运算符与表达式基础 +- 控制流:条件与循环 +reading_time_minutes: 18 +tags: +- host +- cpp-modern +- intermediate +- 进阶 +- 基础 +title: C 语言陷阱与常见错误 --- - # C 语言陷阱与常见错误 说实话,笔者在学 C 语言的时候,踩过的坑比写过的正确代码还多。C 语言的设计哲学是"信任程序员"——编译器不会拦着你做蠢事,它只会默默地把蠢事编译成机器码,然后看着你 segfault。K&R 时代的很多设计决策在今天看来已经有些"古老"了,但为了向下兼容,这些陷阱被一代代地保留了下来,成为每个 C/C++ 程序员的必修课。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/04-oop-in-c.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/04-oop-in-c.md index 06ba83e8b..0be6498bf 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/04-oop-in-c.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/04-oop-in-c.md @@ -1,24 +1,24 @@ --- -title: "用 C 实现面向对象编程" -description: "用结构体 + 函数指针模拟类、封装、继承与多态,理解 OOP 的底层实现机制" chapter: 1 -order: 104 -tags: - - host - - cpp-modern - - advanced - - 实战 - - 基础 +cpp_standard: +- 11 +description: 用结构体 + 函数指针模拟类、封装、继承与多态,理解 OOP 的底层实现机制 difficulty: advanced +order: 104 platform: host -reading_time_minutes: 25 -cpp_standard: [11] prerequisites: - - "指针进阶:多级指针、指针与 const" - - "结构体、联合体与内存对齐" - - "函数指针与回调机制" +- 指针进阶:多级指针、指针与 const +- 结构体、联合体与内存对齐 +- 函数指针与回调机制 +reading_time_minutes: 15 +tags: +- host +- cpp-modern +- advanced +- 实战 +- 基础 +title: 用 C 实现面向对象编程 --- - # 用 C 实现面向对象编程 说实话,这个话题笔者纠结了很久要不要写。毕竟都 2026 年了,谁还在 C 里手搓 OOP?但后来想想——嵌入式开发、Linux 内核、GTK/GLib、Lua 源码,这些重量级的 C 项目哪一个不是在用 struct + 函数指针做面向对象?更关键的是,如果你不理解 C 层面 OOP 是怎么拼出来的,那学 C++ 的时候对虚函数表、vptr、动态绑定的理解就永远是空中楼阁——你知道语法怎么用,但不知道底下发生了什么。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/05-handmade-dynamic-array.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/05-handmade-dynamic-array.md index 97c993ef4..4e58dd2d0 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/05-handmade-dynamic-array.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/05-handmade-dynamic-array.md @@ -1,26 +1,28 @@ --- -title: "手搓动态数组——从零实现容器" -description: "从零设计和实现一个类型安全的动态数组库,理解内存扩缩容策略、错误处理模式与 API 设计原则,打通通向 std::vector 的理解之路" chapter: 1 -order: 105 -tags: - - host - - cpp-modern - - intermediate - - 进阶 - - 容器 - - 内存管理 +cpp_standard: +- 11 +- 14 +- 17 +description: 从零设计和实现一个类型安全的动态数组库,理解内存扩缩容策略、错误处理模式与 API 设计原则,打通通向 std::vector 的理解之路 difficulty: intermediate +order: 105 platform: host -reading_time_minutes: 30 -cpp_standard: [11, 14, 17] prerequisites: - - "指针进阶:多级指针、指针与 const" - - "动态内存管理:malloc/free/realloc 的正确使用" - - "结构体、联合体与内存对齐" - - "C 语言陷阱与常见错误" +- 指针进阶:多级指针、指针与 const +- 动态内存管理:malloc/free/realloc 的正确使用 +- 结构体、联合体与内存对齐 +- C 语言陷阱与常见错误 +reading_time_minutes: 18 +tags: +- host +- cpp-modern +- intermediate +- 进阶 +- 容器 +- 内存管理 +title: 手搓动态数组——从零实现容器 --- - # 手搓动态数组——从零实现容器 我们在写 C 程序的时候,最痛苦的事情之一就是数组大小必须在编译期确定。你想存 10 个数据,声明 `int arr[10]`;后来需求变了要存 100 个,你得回去改代码重新编译。更要命的是,很多时候你压根就不知道运行时会有多少个数据入列——用户输入多少条记录、网络收到多少个包、传感器采集多少个样本,这些都是运行时才能确定的。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/06-handmade-linked-list.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/06-handmade-linked-list.md index 22743a963..5b1f2915c 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/06-handmade-linked-list.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/06-handmade-linked-list.md @@ -1,25 +1,25 @@ --- -title: "手搓单链表——指针与内存的实战" -description: "从零实现经典单链表,掌握插入、删除、查找算法与哨兵节点技巧" chapter: 1 -order: 106 -tags: - - host - - cpp-modern - - advanced - - 实战 - - 内存管理 - - 智能指针 +cpp_standard: +- 11 +description: 从零实现经典单链表,掌握插入、删除、查找算法与哨兵节点技巧 difficulty: advanced +order: 106 platform: host -reading_time_minutes: 30 -cpp_standard: [11] prerequisites: - - "手搓动态数组——malloc 与 realloc 实战" - - "指针到底在指什么" - - "C 语言陷阱与常见错误" +- 手搓动态数组——malloc 与 realloc 实战 +- 指针到底在指什么 +- C 语言陷阱与常见错误 +reading_time_minutes: 26 +tags: +- host +- cpp-modern +- advanced +- 实战 +- 内存管理 +- 智能指针 +title: 手搓单链表——指针与内存的实战 --- - # 手搓单链表——指针与内存的实战 到目前为止我们已经折腾过动态数组了,那一篇里我们用 `malloc` 和 `realloc` 管理一块连续内存,体验了一把"手动挡"内存管理的乐趣。但是连续内存有一个天生的限制——在中间插入和删除元素的时候,你需要把后面所有的数据都挪一遍,时间复杂度 O(n)。对于频繁插入删除的场景,这显然不够优雅。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/07-embedded-c-patterns.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/07-embedded-c-patterns.md index 319b543f1..17e4e8e79 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/07-embedded-c-patterns.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/07-embedded-c-patterns.md @@ -1,24 +1,25 @@ --- -title: "嵌入式 C 编程模式" -description: "寄存器访问模式、volatile 正确使用、中断安全编程、外设抽象层设计与裸机开发模式" chapter: 1 -order: 107 -tags: - - host - - cpp-modern - - intermediate - - 嵌入式 - - 单片机 +cpp_standard: +- 11 +- 17 +description: 寄存器访问模式、volatile 正确使用、中断安全编程、外设抽象层设计与裸机开发模式 difficulty: intermediate +order: 107 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 17] prerequisites: - - "结构体、联合体与内存对齐" - - "函数指针与回调机制" - - "指针进阶:多级指针、指针与 const" +- 结构体、联合体与内存对齐 +- 函数指针与回调机制 +- 指针进阶:多级指针、指针与 const +reading_time_minutes: 16 +tags: +- host +- cpp-modern +- intermediate +- 嵌入式 +- 单片机 +title: 嵌入式 C 编程模式 --- - # 嵌入式 C 编程模式 写桌面程序的时候,我们基本上不需要关心编译器会不会偷偷把一次内存读操作给优化掉、或者两段代码会不会在同一时刻踩同一块数据。但一旦你把目光投向裸机——没有操作系统、没有标准库、甚至没有 `main` 的标准入口——这些问题就全冒出来了。嵌入式 C 编程有一套自己的模式语言:寄存器用结构体映射,硬件状态必须用 `volatile` 保护,中断和主循环之间的数据交换需要精心设计的同步机制。 diff --git a/documents/vol1-fundamentals/c_tutorials/advanced_feature/08-reusable-c-code.md b/documents/vol1-fundamentals/c_tutorials/advanced_feature/08-reusable-c-code.md index 9a0e8132d..b31e0db28 100644 --- a/documents/vol1-fundamentals/c_tutorials/advanced_feature/08-reusable-c-code.md +++ b/documents/vol1-fundamentals/c_tutorials/advanced_feature/08-reusable-c-code.md @@ -13,7 +13,7 @@ prerequisites: - 指针进阶:不完整类型与多级指针 - 结构体与内存布局 - 编译与链接基础 -reading_time_minutes: 32 +reading_time_minutes: 24 tags: - host - cpp-modern diff --git a/documents/vol1-fundamentals/ch00/00-preface.md b/documents/vol1-fundamentals/ch00/00-preface.md index ce6e242fe..1fca7faf7 100644 --- a/documents/vol1-fundamentals/ch00/00-preface.md +++ b/documents/vol1-fundamentals/ch00/00-preface.md @@ -1,19 +1,23 @@ --- -title: "前言:为什么学 C++" -description: "了解 C++ 的核心价值、应用领域与学习路线,开启现代 C++ 之旅" chapter: 0 -order: 0 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 了解 C++ 的核心价值、应用领域与学习路线,开启现代 C++ 之旅 difficulty: beginner -reading_time_minutes: 8 +order: 0 platform: host +reading_time_minutes: 12 tags: - - cpp-modern - - host - - beginner - - 入门 -cpp_standard: [11, 14, 17, 20, 23] +- cpp-modern +- host +- beginner +- 入门 +title: 前言:为什么学 C++ --- - # 前言:为什么学 C++ 说实话,写这篇前言的时候我想了很久——到底该用什么语气来开这个头。如果只是冷冰冰地列一堆"C++ 很强"的理由,那跟翻 Wikipedia 没什么区别,实在没什么意思。所以我想换一种方式:聊聊我自己为什么要折腾 C++,以及为什么我觉得在 2026 年的今天,C++ 依然值得你花时间认真学习。 diff --git a/documents/vol1-fundamentals/ch00/01-setup-linux.md b/documents/vol1-fundamentals/ch00/01-setup-linux.md index 96915c3da..571024583 100644 --- a/documents/vol1-fundamentals/ch00/01-setup-linux.md +++ b/documents/vol1-fundamentals/ch00/01-setup-linux.md @@ -1,20 +1,23 @@ --- -title: "Linux 环境搭建" -description: "在 Linux 上搭建 C++ 开发环境:安装编译器、CMake 和 VS Code,从零配置到编译运行第一个程序" chapter: 0 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 在 Linux 上搭建 C++ 开发环境:安装编译器、CMake 和 VS Code,从零配置到编译运行第一个程序 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host +reading_time_minutes: 12 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: Linux 环境搭建 --- - # Linux 环境搭建 在开始写 C++ 之前,我们得先把工位收拾好。这一篇要做的事情很简单——在 Linux 上从零搭建一套能编译、能构建、能舒服写代码的 C++ 开发环境。整个过程大概十五分钟,但如果你是第一次折腾 Linux 环境配置,留半小时,嗯,说不定也有可能是一天,比较稳妥。前提是你早就熟悉Linux了,不熟悉Linux的朋友下一篇——Windows部署走起。 diff --git a/documents/vol1-fundamentals/ch00/02-setup-windows.md b/documents/vol1-fundamentals/ch00/02-setup-windows.md index f26eb888f..e736aeab9 100644 --- a/documents/vol1-fundamentals/ch00/02-setup-windows.md +++ b/documents/vol1-fundamentals/ch00/02-setup-windows.md @@ -1,20 +1,23 @@ --- -title: "Windows 环境搭建" -description: "在 Windows 上搭建 C++ 开发环境:安装 Visual Studio 或 MinGW、配置 CMake 和 vcpkg,从零开始到编译运行" chapter: 0 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 在 Windows 上搭建 C++ 开发环境:安装 Visual Studio 或 MinGW、配置 CMake 和 vcpkg,从零开始到编译运行 difficulty: beginner -reading_time_minutes: 15 +order: 2 platform: host +reading_time_minutes: 12 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: Windows 环境搭建 --- - # Windows 环境搭建 > ⚠LLM:这部分内容笔者没有精力仔细验证,懂行的朋友欢迎批评指正! diff --git a/documents/vol1-fundamentals/ch00/03-first-program.md b/documents/vol1-fundamentals/ch00/03-first-program.md index dd7276fb7..6c6182a82 100644 --- a/documents/vol1-fundamentals/ch00/03-first-program.md +++ b/documents/vol1-fundamentals/ch00/03-first-program.md @@ -1,23 +1,26 @@ --- -title: "第一个 C++ 程序" -description: "编写、编译和运行你的第一个 C++ 程序,理解 main 函数、输入输出和编译流程" chapter: 0 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 编写、编译和运行你的第一个 C++ 程序,理解 main 函数、输入输出和编译流程 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "Linux 环境搭建" - - "Windows 环境搭建" +- Linux 环境搭建 +- Windows 环境搭建 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 第一个 C++ 程序 --- - # 第一个 C++ 程序 环境搭好了,编译器也装好了,接下来该干正事了——写我们的第一行 C++ 代码。 diff --git a/documents/vol1-fundamentals/ch01/01-basic-types.md b/documents/vol1-fundamentals/ch01/01-basic-types.md index 32e3edc75..7b08895a9 100644 --- a/documents/vol1-fundamentals/ch01/01-basic-types.md +++ b/documents/vol1-fundamentals/ch01/01-basic-types.md @@ -1,22 +1,25 @@ --- -title: "基本数据类型" -description: "掌握 C++ 的整数、浮点、字符和布尔类型,理解类型大小、取值范围与平台差异" chapter: 1 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 C++ 的整数、浮点、字符和布尔类型,理解类型大小、取值范围与平台差异 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "第一个 C++ 程序" +- 第一个 C++ 程序 +reading_time_minutes: 17 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 基本数据类型 --- - # 基本数据类型 上一章我们写出了第一个 C++ 程序,用 `int` 声明了整数变量,用 `std::cin` 和 `std::cout` 完成了输入输出。你可能当时就在想:`int` 到底能存多大的数?小数怎么办?文字怎么表示?这些问题非常好,因为它们直指 C++ 类型系统的核心——我们这一章就来彻底搞清楚 C++ 给我们提供了哪些基本数据类型,每种类型能存什么、存多少、边界在哪里。 diff --git a/documents/vol1-fundamentals/ch01/02-type-conversion.md b/documents/vol1-fundamentals/ch01/02-type-conversion.md index cd7523ccc..9916b5037 100644 --- a/documents/vol1-fundamentals/ch01/02-type-conversion.md +++ b/documents/vol1-fundamentals/ch01/02-type-conversion.md @@ -1,22 +1,25 @@ --- -title: "类型转换" -description: "理解 C++ 的隐式转换与显式转换规则,掌握 static_cast 的使用,避开类型转换中的经典陷阱" chapter: 1 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解 C++ 的隐式转换与显式转换规则,掌握 static_cast 的使用,避开类型转换中的经典陷阱 difficulty: beginner -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "基本数据类型" +- 基本数据类型 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 类型转换 --- - # 类型转换 写过几行 C++ 代码之后,你一定会碰到这样的情况:一个 `int` 需要变成 `double`,一个 `double` 需要截断成 `int`,或者一个有符号数和一个无符号数在做比较。类型转换在真实的程序里几乎无处不在——而你如果不理解它的规则,编译器就会在背后悄悄替你做决定,然后你在某个深夜收获一个完全看不懂的 bug。 diff --git a/documents/vol1-fundamentals/ch01/03-const-basics.md b/documents/vol1-fundamentals/ch01/03-const-basics.md index 3a7377f13..5f387ad52 100644 --- a/documents/vol1-fundamentals/ch01/03-const-basics.md +++ b/documents/vol1-fundamentals/ch01/03-const-basics.md @@ -1,22 +1,25 @@ --- -title: "const 初探" -description: "掌握 const 修饰变量和指针的各种用法,初步了解 constexpr 编译期常量" chapter: 1 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 const 修饰变量和指针的各种用法,初步了解 constexpr 编译期常量 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "类型转换" +- 类型转换 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: const 初探 --- - # const 初探 写代码的时候,有些东西就是不应该被改动的——配置参数一旦设定就不应该被意外覆盖,数组的容量声明之后就不应该再变化,圆周率这种物理常数就更不用说了。如果我们全靠"自觉"来保证这些值不被修改,那跟闭着眼睛走夜路没什么区别,迟早有一天会手滑改掉某个关键值,然后花半天时间去排查一个莫名其妙的 bug。 diff --git a/documents/vol1-fundamentals/ch01/04-value-categories.md b/documents/vol1-fundamentals/ch01/04-value-categories.md index 0bdc0d88d..815d5d7ac 100644 --- a/documents/vol1-fundamentals/ch01/04-value-categories.md +++ b/documents/vol1-fundamentals/ch01/04-value-categories.md @@ -1,22 +1,25 @@ --- -title: "值类别简介" -description: "理解左值与右值的概念,掌握引用的基本用法,为后续移动语义打下基础" chapter: 1 -order: 4 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解左值与右值的概念,掌握引用的基本用法,为后续移动语义打下基础 difficulty: beginner -reading_time_minutes: 12 +order: 4 platform: host prerequisites: - - "const 初探" +- const 初探 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 值类别简介 --- - # 值类别简介 到这一章为止,我们已经跟变量、类型、const 打过不少交道了。但你有没有想过一个问题:为什么有些表达式能放在赋值号左边,有些就只能放在右边?为什么 `int& ref = x;` 编译得过,`int& ref = 42;` 就编译不过?这些看似零散的现象,背后其实有一条统一的线索——**值类别**(value category)。 diff --git a/documents/vol1-fundamentals/ch02/01-conditionals.md b/documents/vol1-fundamentals/ch02/01-conditionals.md index 2c6f8996c..8ff0eb670 100644 --- a/documents/vol1-fundamentals/ch02/01-conditionals.md +++ b/documents/vol1-fundamentals/ch02/01-conditionals.md @@ -1,22 +1,25 @@ --- -title: "条件语句" -description: "掌握 if/else、switch 和三元运算符,学会用条件语句控制程序走向" chapter: 2 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 if/else、switch 和三元运算符,学会用条件语句控制程序走向 difficulty: beginner -reading_time_minutes: 12 +order: 1 platform: host prerequisites: - - "值类别简介" +- 值类别简介 +reading_time_minutes: 10 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 条件语句 --- - # 条件语句 额,你写程序,不可能没有if/else,对吧。如果程序永远只按照一条直线走到底,那它就和一个只会复读的机器没什么区别。现实中的程序需要做判断——"用户输入了负数?那就提示错误""传感器读数超过阈值?那就触发报警"。条件语句就是赋予程序这种"做决定"能力的机制。 diff --git a/documents/vol1-fundamentals/ch02/02-loops.md b/documents/vol1-fundamentals/ch02/02-loops.md index 284ee286b..f8c5c84d0 100644 --- a/documents/vol1-fundamentals/ch02/02-loops.md +++ b/documents/vol1-fundamentals/ch02/02-loops.md @@ -1,22 +1,25 @@ --- -title: "循环语句" -description: "掌握 for、while、do-while 循环和 break/continue 控制,学会让程序重复执行任务" chapter: 2 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 for、while、do-while 循环和 break/continue 控制,学会让程序重复执行任务 difficulty: beginner -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "条件语句" +- 条件语句 +reading_time_minutes: 11 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 循环语句 --- - # 循环语句 计算机最擅长的事情就是不知疲倦地重复做同一件事。倒不如说,计算机就是由无穷无尽的数据存,取,不倦的判断0和1,然后循环的做他们,组成了我们的互联网世界! diff --git a/documents/vol1-fundamentals/ch02/03-range-for.md b/documents/vol1-fundamentals/ch02/03-range-for.md index ddc54ac92..f1e017567 100644 --- a/documents/vol1-fundamentals/ch02/03-range-for.md +++ b/documents/vol1-fundamentals/ch02/03-range-for.md @@ -1,22 +1,25 @@ --- -title: "range-for 循环" -description: "掌握 C++11 引入的 range-for 循环,用最简洁的方式遍历数组和容器" chapter: 2 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 C++11 引入的 range-for 循环,用最简洁的方式遍历数组和容器 difficulty: beginner -reading_time_minutes: 10 +order: 3 platform: host prerequisites: - - "循环语句" +- 循环语句 +reading_time_minutes: 9 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: range-for 循环 --- - # range-for 循环 写传统的 for 循环遍历数组时,我们总要做一件事——管好那个索引变量。`for (int i = 0; i < n; ++i)`,这行代码我们写过无数遍,但也写错过无数遍:`<` 写成 `<=` 导致越界访问,`i` 忘了自增导致死循环,数组长度改了但循环条件忘了跟着改......说实话,这种因为手滑引入的 bug 最让人头疼,因为它不是逻辑错误,纯粹是体力活没干好。 diff --git a/documents/vol1-fundamentals/ch03/01-function-basics.md b/documents/vol1-fundamentals/ch03/01-function-basics.md index c228ab841..6abb769ac 100644 --- a/documents/vol1-fundamentals/ch03/01-function-basics.md +++ b/documents/vol1-fundamentals/ch03/01-function-basics.md @@ -1,22 +1,25 @@ --- -title: "函数基础" -description: "掌握 C++ 函数的定义、声明、参数传递和返回值,理解作用域与生命周期" chapter: 3 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 C++ 函数的定义、声明、参数传递和返回值,理解作用域与生命周期 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "range-for 循环" +- range-for 循环 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 函数基础 --- - # 函数基础 孩子们,我见过的,有人写代码真的会写一个一万行的程序从头到尾只有 `main()` 一个函数,所有代码像面条一样堆在一起。显然,这个人不太懂函数(小白除外) diff --git a/documents/vol1-fundamentals/ch03/03-overloading-default.md b/documents/vol1-fundamentals/ch03/03-overloading-default.md index f69c72f64..7a8c3deeb 100644 --- a/documents/vol1-fundamentals/ch03/03-overloading-default.md +++ b/documents/vol1-fundamentals/ch03/03-overloading-default.md @@ -1,22 +1,25 @@ --- -title: "重载与默认参数" -description: "掌握函数重载的规则和默认参数的用法,理解重载决议机制,避免两者的常见冲突" chapter: 3 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握函数重载的规则和默认参数的用法,理解重载决议机制,避免两者的常见冲突 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "参数传递方式" +- 参数传递方式 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 重载与默认参数 --- - # 重载与默认参数 上一章我们搞清楚了参数传递的几种方式——值传递、指针传递、引用传递。现在问题来了:假设我们要写一个 `print` 函数,打印整数、打印浮点数、打印字符串,这三件事本质上都是"打印",但 C 语言的规矩是每个函数必须有一个独一无二的名字。于是你就得写 `print_int()`、`print_float()`、`print_string()`——光是起名字就够让人崩溃的,调用的时候还得自己判断该用哪个。 diff --git a/documents/vol1-fundamentals/ch04/01-pointer-basics.md b/documents/vol1-fundamentals/ch04/01-pointer-basics.md index 37394bc52..3feecb66f 100644 --- a/documents/vol1-fundamentals/ch04/01-pointer-basics.md +++ b/documents/vol1-fundamentals/ch04/01-pointer-basics.md @@ -1,22 +1,25 @@ --- -title: "指针基础" -description: "从零开始理解指针:取地址、解引用、指针类型与空指针,掌握 C++ 内存访问的核心机制" chapter: 4 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从零开始理解指针:取地址、解引用、指针类型与空指针,掌握 C++ 内存访问的核心机制 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "inline 与 constexpr 函数" +- inline 与 constexpr 函数 +reading_time_minutes: 11 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 指针基础 --- - # 指针基础 指针大概是 C++ 里名声最响、也最容易劝退新手的特性了。如果你之前接触过 Python 或 Java,很可能习惯了"变量就是对象本身"的思维——变量里存的就是数据,拿来用就好。但 C++ 不一样,它给了我们直接操作内存地址的能力,而指针就是这个能力的入口。 diff --git a/documents/vol1-fundamentals/ch04/02-pointer-arithmetic.md b/documents/vol1-fundamentals/ch04/02-pointer-arithmetic.md index c3a813ccf..84147e346 100644 --- a/documents/vol1-fundamentals/ch04/02-pointer-arithmetic.md +++ b/documents/vol1-fundamentals/ch04/02-pointer-arithmetic.md @@ -1,22 +1,25 @@ --- -title: "指针运算与数组" -description: "掌握指针算术运算、指针与数组的关系,以及 C 风格字符串的指针操作" chapter: 4 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握指针算术运算、指针与数组的关系,以及 C 风格字符串的指针操作 difficulty: beginner -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "指针基础" +- 指针基础 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 指针运算与数组 --- - # 指针运算与数组 如果你已经理解了"指针就是地址"这件事,那么接下来我们要面对一个更深层的真相——在 C++ 里,指针和数组**在最底层的本质上**几乎是同一枚硬币的两面。(笔者很不建议混淆指针和数组的概念,因为这只会在工程逻辑上害人) diff --git a/documents/vol1-fundamentals/ch04/03-references.md b/documents/vol1-fundamentals/ch04/03-references.md index 04e073846..04683e947 100644 --- a/documents/vol1-fundamentals/ch04/03-references.md +++ b/documents/vol1-fundamentals/ch04/03-references.md @@ -1,22 +1,25 @@ --- -title: "引用" -description: "深入理解 C++ 引用:引用的语法、引用与指针的区别,以及 const 引用在函数参数中的重要作用" chapter: 4 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 深入理解 C++ 引用:引用的语法、引用与指针的区别,以及 const 引用在函数参数中的重要作用 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "指针运算与数组" +- 指针运算与数组 +reading_time_minutes: 15 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 引用 --- - # 引用 指针很强大,但说实话,也很容易惹麻烦。上一章我们花了大量篇幅和指针打交道——解引用、取地址、空指针检查、`->` 操作符......写多了你就会发现,很多场景下我们并不需要指针的全部能力。我们只是想"给函数传一个大对象但不想拷贝",或者"让函数修改调用者的变量"。这些需求用指针当然能做,但语法上总显得笨重。C++ 给了我们一个更安全、更简洁的替代方案:**引用**。这一章我们就来把引用从头到尾搞清楚。 diff --git a/documents/vol1-fundamentals/ch04/04-smart-ptr-preview.md b/documents/vol1-fundamentals/ch04/04-smart-ptr-preview.md index 21eb5c570..1ace27d93 100644 --- a/documents/vol1-fundamentals/ch04/04-smart-ptr-preview.md +++ b/documents/vol1-fundamentals/ch04/04-smart-ptr-preview.md @@ -1,22 +1,25 @@ --- -title: "智能指针预告" -description: "了解为什么需要智能指针,初步认识 unique_ptr 如何自动管理内存,为卷二的深入学习埋下伏笔" chapter: 4 -order: 4 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 了解为什么需要智能指针,初步认识 unique_ptr 如何自动管理内存,为卷二的深入学习埋下伏笔 difficulty: beginner -reading_time_minutes: 10 +order: 4 platform: host prerequisites: - - "引用" +- 引用 +reading_time_minutes: 9 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 智能指针预告 --- - # 智能指针预告 到目前为止,我们已经和裸指针打了好几章的交道了。指针确实强大,但也确实危险——每次 `new` 了一块内存,就得时刻记着 `delete` 掉它,中间任何路径漏掉了就是内存泄漏。现代 C++ 给出了一套系统性的解决方案:**智能指针(smart pointer)**。这一章我们先不深入,只是带你认识一下它解决什么问题、基本用法长什么样。真正的全面讲解放在卷二,和移动语义、RAII 一起系统展开。 diff --git a/documents/vol1-fundamentals/ch05/01-c-arrays.md b/documents/vol1-fundamentals/ch05/01-c-arrays.md index f0d2557ca..33e6f9120 100644 --- a/documents/vol1-fundamentals/ch05/01-c-arrays.md +++ b/documents/vol1-fundamentals/ch05/01-c-arrays.md @@ -1,22 +1,25 @@ --- -title: "C 风格数组" -description: "掌握 C 风格数组的声明、初始化和多维用法,理解数组退化及其对函数传参的影响" chapter: 5 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 C 风格数组的声明、初始化和多维用法,理解数组退化及其对函数传参的影响 difficulty: beginner -reading_time_minutes: 12 +order: 1 platform: host prerequisites: - - "智能指针预告" +- 智能指针预告 +reading_time_minutes: 10 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: C 风格数组 --- - # C 风格数组 到目前为止,我们处理数据的方式都是"一个变量存一个值"。但现实世界的数据很少是孤立存在的——一组传感器读数、一串字符、一个矩阵、一张成绩表,这些东西天然就是"一堆相同类型的数据排成一排"。数组(array)就是 C 和 C++ 提供的最原始的、用来存储这种"同类型连续数据"的机制。 diff --git a/documents/vol1-fundamentals/ch05/03-std-string.md b/documents/vol1-fundamentals/ch05/03-std-string.md index 5e99751dc..ced3e8516 100644 --- a/documents/vol1-fundamentals/ch05/03-std-string.md +++ b/documents/vol1-fundamentals/ch05/03-std-string.md @@ -1,22 +1,25 @@ --- -title: "std::string" -description: "掌握 std::string 的构造、拼接、查找和子串操作,学会在 C++ 中安全高效地处理字符串" chapter: 5 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 std::string 的构造、拼接、查找和子串操作,学会在 C++ 中安全高效地处理字符串 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "std::array" +- std::array +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: std::string --- - # std::string 在上一篇教程里,我们花了大把篇幅和 C 风格字符串搏斗——手动管理 `\0` 终止符、小心翼翼地防止缓冲区溢出、用 `strncpy` 和 `snprintf` 如履薄冰地操作每一段字符数组。如果你跟我一样被这些折腾得够呛,那么接下来这个消息会让你松一大口气:C++ 标准库给我们准备了一个真正的字符串类型,叫做 `std::string`,它自动管理内存、自动处理长度、支持直观的拼接和比较,基本上把我们之前在 C 里踩过的坑全部填平了。 diff --git a/documents/vol1-fundamentals/ch06/01-class-basics.md b/documents/vol1-fundamentals/ch06/01-class-basics.md index 64475f7de..e13c6633d 100644 --- a/documents/vol1-fundamentals/ch06/01-class-basics.md +++ b/documents/vol1-fundamentals/ch06/01-class-basics.md @@ -1,22 +1,25 @@ --- -title: "类的定义" -description: "从 struct 到 class:掌握 C++ 类的定义、成员变量与函数、访问控制的基本用法" chapter: 6 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从 struct 到 class:掌握 C++ 类的定义、成员变量与函数、访问控制的基本用法 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "std::string" +- std::string +reading_time_minutes: 14 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 类的定义 --- - # 类的定义 在前面的章节里,我们用 `std::string` 处理文本、用 `std::array` 管理固定大小的集合——这些类型用起来方便,但它们到底是怎么被"发明"出来的?答案是类。`std::string` 本身就是一个类,`std::array` 也是一个类,C++ 标准库里几乎所有的工具都是用类来构建的。我们当然可以说,类是 C++ 最核心的抽象机制:它把"数据"和"操作数据的函数"打包成一个整体,让我们能够像使用内置类型一样使用自定义类型。 diff --git a/documents/vol1-fundamentals/ch06/02-constructors.md b/documents/vol1-fundamentals/ch06/02-constructors.md index d44de1c36..308f979f0 100644 --- a/documents/vol1-fundamentals/ch06/02-constructors.md +++ b/documents/vol1-fundamentals/ch06/02-constructors.md @@ -1,22 +1,25 @@ --- -title: "构造函数" -description: "掌握默认构造、参数化构造、拷贝构造、初始化列表和委托构造的完整用法" chapter: 6 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握默认构造、参数化构造、拷贝构造、初始化列表和委托构造的完整用法 difficulty: beginner -reading_time_minutes: 15 +order: 2 platform: host prerequisites: - - "类的定义" +- 类的定义 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 构造函数 --- - # 构造函数 上一章我们学了怎么定义一个类——写成员变量、写成员函数、用 `public` 和 `private` 控制访问权限。但有一个问题我们一直绕过去了:对象被创建出来的时候,它的成员变量里装的是什么?答案是——如果你什么都不做,局部对象的成员变量里装的是 **垃圾值!** ,是上一次那块内存里残留的随机数据。 diff --git a/documents/vol1-fundamentals/ch06/03-destructors.md b/documents/vol1-fundamentals/ch06/03-destructors.md index 40089fa9f..0b2b955d2 100644 --- a/documents/vol1-fundamentals/ch06/03-destructors.md +++ b/documents/vol1-fundamentals/ch06/03-destructors.md @@ -1,22 +1,25 @@ --- -title: "析构函数与资源管理" -description: "理解析构函数的调用时机,初步认识 RAII 原则和 Rule of Three 的设计思路" chapter: 6 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解析构函数的调用时机,初步认识 RAII 原则和 Rule of Three 的设计思路 difficulty: beginner -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "构造函数" +- 构造函数 +reading_time_minutes: 10 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 析构函数与资源管理 --- - # 析构函数与资源管理 构造函数负责把对象带入一个合法的状态——分配内存、打开文件、初始化硬件。但所有这些资源都有一个共同的问题:它们必须在某个时刻被归还。`malloc` 了不 `free`、`fopen` 了不 `fclose`、锁了互斥量不解锁——程序就会慢慢泄漏资源,最终耗尽系统配额或者陷入死锁。 diff --git a/documents/vol1-fundamentals/ch06/04-static-members.md b/documents/vol1-fundamentals/ch06/04-static-members.md index 2361c2287..52186a6e2 100644 --- a/documents/vol1-fundamentals/ch06/04-static-members.md +++ b/documents/vol1-fundamentals/ch06/04-static-members.md @@ -1,22 +1,25 @@ --- -title: "static 成员" -description: "掌握类的 static 变量与函数,理解类级别的共享状态与单例模式的初步思想" chapter: 6 -order: 4 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握类的 static 变量与函数,理解类级别的共享状态与单例模式的初步思想 difficulty: beginner -reading_time_minutes: 10 +order: 4 platform: host prerequisites: - - "析构函数与资源管理" +- 析构函数与资源管理 +reading_time_minutes: 11 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: static 成员 --- - # static 成员 到现在为止,我们接触的所有成员变量和成员函数都绑定在"对象"上——每创建一个 `Sensor`,就多一份 `pin`、多一份 `cached_value`,它们各自独立、互不干扰。但在实际工程里,有一类数据和操作天然就不属于某个具体对象,而是属于**整个类**。比如:当前系统里到底创建了多少个 `UARTPort` 实例?硬件抽象层有没有初始化过?所有 `Sensor` 共享的默认采样频率是多少? diff --git a/documents/vol1-fundamentals/ch06/06-this-and-cascading.md b/documents/vol1-fundamentals/ch06/06-this-and-cascading.md index 0fd216565..c987bc785 100644 --- a/documents/vol1-fundamentals/ch06/06-this-and-cascading.md +++ b/documents/vol1-fundamentals/ch06/06-this-and-cascading.md @@ -1,22 +1,25 @@ --- -title: "this 指针与链式调用" -description: "理解 this 指针的本质,掌握链式调用模式和 const 成员函数的正确用法" chapter: 6 -order: 6 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解 this 指针的本质,掌握链式调用模式和 const 成员函数的正确用法 difficulty: beginner -reading_time_minutes: 10 +order: 6 platform: host prerequisites: - - "友元" +- 友元 +reading_time_minutes: 11 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: this 指针与链式调用 --- - # this 指针与链式调用 到目前为止,我们写的类都有一个默契——成员函数"知道"自己操作的是哪个对象。调用 `led.on()`,`on()` 就操作 `led`;调用 `other_led.on()`,`on()` 就操作 `other_led`。同一个函数,不同对象调用,行为各不相同。这件事看起来理所当然,但背后的机制值得深挖:编译器到底怎么让一个函数"知道"调用者是谁的? diff --git a/documents/vol1-fundamentals/ch07/01-arithmetic-comparison.md b/documents/vol1-fundamentals/ch07/01-arithmetic-comparison.md index 92096b00f..99f23bf5a 100644 --- a/documents/vol1-fundamentals/ch07/01-arithmetic-comparison.md +++ b/documents/vol1-fundamentals/ch07/01-arithmetic-comparison.md @@ -1,19 +1,23 @@ --- -title: "算术与比较运算符" -description: "掌握算术运算符和比较运算符的重载方法,实现一个完整的 Fraction 分数类" chapter: 7 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握算术运算符和比较运算符的重载方法,实现一个完整的 Fraction 分数类 difficulty: intermediate -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "this 指针与链式调用" +- this 指针与链式调用 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 算术与比较运算符 --- # 算术与比较运算符 diff --git a/documents/vol1-fundamentals/ch07/02-io-subscript.md b/documents/vol1-fundamentals/ch07/02-io-subscript.md index efbbb4f6d..ba80ca977 100644 --- a/documents/vol1-fundamentals/ch07/02-io-subscript.md +++ b/documents/vol1-fundamentals/ch07/02-io-subscript.md @@ -1,19 +1,23 @@ --- -title: "流与下标运算符" -description: "掌握 << >> 重载和 operator[] 的实现,让自定义类型支持流式 I/O 和索引访问" chapter: 7 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 << >> 重载和 operator[] 的实现,让自定义类型支持流式 I/O 和索引访问 difficulty: intermediate -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "算术与比较运算符" +- 算术与比较运算符 +reading_time_minutes: 10 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 流与下标运算符 --- # 流与下标运算符 diff --git a/documents/vol1-fundamentals/ch07/03-call-and-conversion.md b/documents/vol1-fundamentals/ch07/03-call-and-conversion.md index dcfb65fe8..5a0a0b5ef 100644 --- a/documents/vol1-fundamentals/ch07/03-call-and-conversion.md +++ b/documents/vol1-fundamentals/ch07/03-call-and-conversion.md @@ -1,21 +1,24 @@ --- -title: "函数调用与类型转换" -description: "掌握 operator() 和类型转换运算符的重载,学会实现函数对象和安全的隐式转换" chapter: 7 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 operator() 和类型转换运算符的重载,学会实现函数对象和安全的隐式转换 difficulty: intermediate -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "流与下标运算符" +- 流与下标运算符 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 函数调用与类型转换 --- - # 函数调用与类型转换 前面的章节里,我们已经让自定义类型支持了算术运算、下标访问、流输入输出——让对象表现得像值、像容器、像可打印的东西。但运算符重载的能力远不止于此。这一章我们要处理的是两个非常有意思的场景:让对象表现得像函数,以及让对象能隐式或显式地"变成"另一种类型。 diff --git a/documents/vol1-fundamentals/ch08/01-single-inheritance.md b/documents/vol1-fundamentals/ch08/01-single-inheritance.md index dfc70adb5..c09d093a8 100644 --- a/documents/vol1-fundamentals/ch08/01-single-inheritance.md +++ b/documents/vol1-fundamentals/ch08/01-single-inheritance.md @@ -1,21 +1,24 @@ --- -title: "单继承" -description: "掌握单继承的语法、构造与析构顺序,理解对象切片问题及其解决方案" chapter: 8 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握单继承的语法、构造与析构顺序,理解对象切片问题及其解决方案 difficulty: intermediate -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "函数调用与类型转换" +- 函数调用与类型转换 +reading_time_minutes: 11 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 单继承 --- - # 单继承 到目前为止,我们写的所有类都是"独立"的——一个类封装自己的数据、提供自己的接口,彼此之间没有亲缘关系。但现实世界的事物并不是孤立存在的:一个学生(Student)是一个人(Person),一辆轿车(Car)是一种交通工具(Vehicle)。这种"is-a"关系就是继承要表达的核心语义。 diff --git a/documents/vol1-fundamentals/ch08/02-virtual-functions.md b/documents/vol1-fundamentals/ch08/02-virtual-functions.md index 3b4aba48e..4d6e86b80 100644 --- a/documents/vol1-fundamentals/ch08/02-virtual-functions.md +++ b/documents/vol1-fundamentals/ch08/02-virtual-functions.md @@ -1,21 +1,24 @@ --- -title: "虚函数与多态" -description: "理解 virtual、override 和 vtable 机制,掌握运行时多态的实现原理与正确用法" chapter: 8 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解 virtual、override 和 vtable 机制,掌握运行时多态的实现原理与正确用法 difficulty: intermediate -reading_time_minutes: 15 +order: 2 platform: host prerequisites: - - "单继承" +- 单继承 +reading_time_minutes: 12 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 虚函数与多态 --- - # 虚函数与多态 上一篇我们学了单继承——派生类继承了基类的成员,可以在其基础上扩展新的行为。但继承本身只解决了一半的问题:如果我们拿着一个基类指针去操作派生类对象,调用的却永远是基类版本的函数,那继承的表达力就被砍了一大截。虚函数正是补上这另一半的关键——它让"通过基类接口调用派生类实现"成为可能,这就是运行时多态。 diff --git a/documents/vol1-fundamentals/ch08/04-multiple-inheritance.md b/documents/vol1-fundamentals/ch08/04-multiple-inheritance.md index 1507eccb5..036fb5f12 100644 --- a/documents/vol1-fundamentals/ch08/04-multiple-inheritance.md +++ b/documents/vol1-fundamentals/ch08/04-multiple-inheritance.md @@ -1,21 +1,24 @@ --- -title: "多继承与虚继承" -description: "理解多继承的语法、菱形继承问题及虚继承的解决方案,学会审慎使用多继承" chapter: 8 -order: 4 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解多继承的语法、菱形继承问题及虚继承的解决方案,学会审慎使用多继承 difficulty: intermediate -reading_time_minutes: 12 +order: 4 platform: host prerequisites: - - "抽象类与接口" +- 抽象类与接口 +reading_time_minutes: 9 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 多继承与虚继承 --- - # 多继承与虚继承 在前面的章节里,我们讨论的都是单继承——一个类只有一个直接基类。这覆盖了绝大多数面向对象设计的需求。但 C++ 还允许一个类同时继承多个基类,这就是多继承(multiple inheritance)。多继承能力强大但争议极大——用好它能让设计更灵活,用不好则会让整个继承体系变得难以维护。(所以比起来笔者更信赖组合的方式) diff --git a/documents/vol1-fundamentals/ch08/05-oop-in-practice.md b/documents/vol1-fundamentals/ch08/05-oop-in-practice.md index 840ac40c8..9aa52b96b 100644 --- a/documents/vol1-fundamentals/ch08/05-oop-in-practice.md +++ b/documents/vol1-fundamentals/ch08/05-oop-in-practice.md @@ -1,21 +1,24 @@ --- -title: "OOP 实战" -description: "综合运用继承、多态和运算符重载,实现一个完整的图形绘制系统,并讨论继承 vs 组合的设计选择" chapter: 8 -order: 5 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 综合运用继承、多态和运算符重载,实现一个完整的图形绘制系统,并讨论继承 vs 组合的设计选择 difficulty: intermediate -reading_time_minutes: 15 +order: 5 platform: host prerequisites: - - "多继承与虚继承" +- 多继承与虚继承 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: OOP 实战 --- - # OOP 实战 到目前为止,我们已经把 OOP 的核心零件全部拆解过一遍了——类与对象、构造与析构、继承与多态、运算符重载、虚继承。每个知识点单独拿出来不算复杂,但真正写项目的时候,这些零件是同时出场、互相配合的。这一章我们换一种玩法:不再零散地讲知识点,而是从头到尾实现一个完整的图形绘制系统,把前面学的所有 OOP 技术一次性串起来,最后还会讨论继承 vs 组合的设计选择。 diff --git a/documents/vol1-fundamentals/ch09/01-function-templates.md b/documents/vol1-fundamentals/ch09/01-function-templates.md index 4cf486ee8..07d52faf2 100644 --- a/documents/vol1-fundamentals/ch09/01-function-templates.md +++ b/documents/vol1-fundamentals/ch09/01-function-templates.md @@ -1,21 +1,24 @@ --- -title: "函数模板" -description: "掌握 template 的语法、实例化机制和类型推导,学会编写泛型函数" chapter: 9 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 template 的语法、实例化机制和类型推导,学会编写泛型函数 difficulty: intermediate -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "OOP 实战" +- OOP 实战 +reading_time_minutes: 16 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 函数模板 --- - # 函数模板 假设我们现在要写一个 `max` 函数,它接受两个值,返回较大的那个。思路很直接——两行代码就能搞定。但如果我们的程序里同时需要比较 `int`、`double` 和 `std::string`,那就要写三个版本:一个 `max(int, int)`,一个 `max(double, double)`,一个 `max(std::string, std::string)`。三个版本的逻辑完全一样,都是 `(a > b) ? a : b`,区别仅仅是参数类型不同。 diff --git a/documents/vol1-fundamentals/ch09/02-class-templates.md b/documents/vol1-fundamentals/ch09/02-class-templates.md index 941f2528b..dbe135330 100644 --- a/documents/vol1-fundamentals/ch09/02-class-templates.md +++ b/documents/vol1-fundamentals/ch09/02-class-templates.md @@ -1,21 +1,24 @@ --- -title: "类模板" -description: "掌握类模板定义、成员函数和模板参数,实现泛型栈" chapter: 9 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握类模板定义、成员函数和模板参数,实现泛型栈 difficulty: intermediate -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "函数模板" +- 函数模板 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 类模板 --- - # 类模板 上一章我们学会了用 `template ` 让函数变成泛型的——一个 `max_value` 就能处理各种类型。但函数模板只能泛化"一段逻辑"。如果我们想要一个泛化的"数据结构"呢?比如一个栈——它的 push、pop、top 操作对各种类型来说逻辑完全一样,但栈内部需要存储一组同类型的元素,这个"类型"在编写类的时候就决定了。C++ 标准库之所以能提供 `std::vector`、`std::vector` 这样灵活的容器,靠的就是类模板(class template)。这也是我们本章的主角!它让我们把类型参数化到整个类的层面——成员变量、成员函数、甚至嵌套类型都可以使用模板参数。这一章我们就来搞清楚类模板的语法、成员函数的定义方式、模板参数的种类,最后手把手实现一个完整的泛型栈。 diff --git a/documents/vol1-fundamentals/ch09/03-specialization-basics.md b/documents/vol1-fundamentals/ch09/03-specialization-basics.md index 64bde568e..89d5d64cd 100644 --- a/documents/vol1-fundamentals/ch09/03-specialization-basics.md +++ b/documents/vol1-fundamentals/ch09/03-specialization-basics.md @@ -1,21 +1,24 @@ --- -title: "模板特化初步" -description: "理解全特化与偏特化的概念,学会为特定类型提供定制化的模板实现" chapter: 9 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解全特化与偏特化的概念,学会为特定类型提供定制化的模板实现 difficulty: intermediate -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "类模板" +- 类模板 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 模板特化初步 --- - # 模板特化初步 模板的强大之处在于"一套代码,多种类型"。但现实工程里,我们经常会碰到这样一种情况:通用版本对大多数类型工作得很好,偏偏有那么几个类型——要么语义不同,要么性能需求不同——需要一份专门定制的实现。比如我们写了一个通用的 `max()` 函数模板,对 `int` 和 `double` 都能正确比较大小,但传进来两个 `const char*` 的时候,它比较的是指针地址而不是字符串内容,这显然不是我们想要的。 diff --git a/documents/vol1-fundamentals/ch10/02-exception-safety.md b/documents/vol1-fundamentals/ch10/02-exception-safety.md index 5228dfa3e..c7c544240 100644 --- a/documents/vol1-fundamentals/ch10/02-exception-safety.md +++ b/documents/vol1-fundamentals/ch10/02-exception-safety.md @@ -1,21 +1,24 @@ --- -title: "异常安全" -description: "理解异常安全的四个等级,掌握 RAII 守卫模式确保资源在异常发生时正确释放" chapter: 10 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解异常安全的四个等级,掌握 RAII 守卫模式确保资源在异常发生时正确释放 difficulty: intermediate -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "异常基础" +- 异常基础 +reading_time_minutes: 14 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 异常安全 --- - # 异常安全 抛出一个异常很容易——`throw std::runtime_error("oops")` 一行就够了。但真正让人头疼的问题是:异常飞过的时候,在此之前已经打开的文件、分配的内存、锁住的互斥量……这些东西谁来收拾?如果没人管,轻则内存泄漏,重则程序状态彻底崩坏。异常安全(exception safety)讨论的就是这件事——不是"怎么抛异常",而是"异常发生后,程序的状态还能不能看"。 diff --git a/documents/vol1-fundamentals/ch10/03-error-handling-comparison.md b/documents/vol1-fundamentals/ch10/03-error-handling-comparison.md index 828e1969f..3846de638 100644 --- a/documents/vol1-fundamentals/ch10/03-error-handling-comparison.md +++ b/documents/vol1-fundamentals/ch10/03-error-handling-comparison.md @@ -1,21 +1,24 @@ --- -title: "错误处理方式对比" -description: "对比异常、错误码、optional 和 expected 的错误处理策略" chapter: 10 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 对比异常、错误码、optional 和 expected 的错误处理策略 difficulty: intermediate -reading_time_minutes: 14 +order: 3 platform: host prerequisites: - - "异常安全" +- 异常安全 +reading_time_minutes: 15 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 错误处理方式对比 --- - # 错误处理方式对比 C++ 这门语言给我们的错误处理工具,说实话,比大多数语言都要多。C 时代我们只有返回值和 `errno`;Java 和 C# 那边几乎全靠异常;Rust 给了 `Result` 和 `?` 操作符。而 C++ 呢?它全都有。错误码、异常、`std::optional`、`std::expected`——工具箱里塞得满满当当。多并不是坏事,但如果我们不理解每种工具的设计意图和取舍,就很容易写出风格混乱的代码:同一个项目里,有的函数返回 `-1`,有的抛异常,有的返回 `std::nullopt`,调用者每次都要翻文档才能知道该怎么处理错误。 diff --git a/documents/vol1-fundamentals/ch11/01-vector.md b/documents/vol1-fundamentals/ch11/01-vector.md index cce12b674..e9a85eee5 100644 --- a/documents/vol1-fundamentals/ch11/01-vector.md +++ b/documents/vol1-fundamentals/ch11/01-vector.md @@ -1,22 +1,25 @@ --- -title: "std::vector 快速上手" -description: "掌握 vector 的增删改查和容量管理,学会使用最常用的 C++ 动态容器" chapter: 11 -order: 1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 vector 的增删改查和容量管理,学会使用最常用的 C++ 动态容器 difficulty: beginner -reading_time_minutes: 15 +order: 1 platform: host prerequisites: - - "错误处理方式对比" +- 错误处理方式对比 +reading_time_minutes: 12 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: std::vector 快速上手 --- - # std::vector 快速上手 前面几章我们把 C++ 的语言核心——类型系统、控制流、函数、类与继承——基本过了一遍。从现在开始,我们要进入一个全新的领域:标准模板库(STL)。STL 提供了一大批现成的容器、算法和迭代器,能让我们少造很多轮子。而在所有容器中,`std::vector` 绝对是出场率最高的那个——动态数组,自动扩容,元素连续存储,随机访问 O(1)。说实话,如果你不确定该用什么容器,用 `vector` 就对了,其他容器都是在特定场景下才有优势。 diff --git a/documents/vol1-fundamentals/ch11/02-map-set.md b/documents/vol1-fundamentals/ch11/02-map-set.md index b3617486a..df96f2d27 100644 --- a/documents/vol1-fundamentals/ch11/02-map-set.md +++ b/documents/vol1-fundamentals/ch11/02-map-set.md @@ -1,22 +1,25 @@ --- -title: "关联容器快速上手" -description: "掌握 std::map、std::set 和 std::unordered_map 的核心操作,学会按键查找和维护有序集合" chapter: 11 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 std::map、std::set 和 std::unordered_map 的核心操作,学会按键查找和维护有序集合 difficulty: beginner -reading_time_minutes: 15 +order: 2 platform: host prerequisites: - - "std::vector 快速上手" +- std::vector 快速上手 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 关联容器快速上手 --- - # 关联容器快速上手 上一章,我们把 `std::vector` 从头到尾过了一遍——动态数组、连续存储、下标随机访问 O(1),处理有序序列的时候它就是主力。但很多场景下我们关心的不是"第几个元素是什么",而是"某个 key 对应的 value 是什么"。比如统计一段文本里每个单词出现了几次,或者检查某个单词是否在拼写词典里——这种"给一个 key,查一个结果"的需求,用 vector 来做的话要么排序后二分查找,要么线性扫描,写起来费劲性能也差。C++ 标准库为我们准备了一组专门解决这类问题的容器,叫做**关联容器**(associative container)。 diff --git a/documents/vol1-fundamentals/ch11/03-algorithms-intro.md b/documents/vol1-fundamentals/ch11/03-algorithms-intro.md index 1ac142f9d..807927e7c 100644 --- a/documents/vol1-fundamentals/ch11/03-algorithms-intro.md +++ b/documents/vol1-fundamentals/ch11/03-algorithms-intro.md @@ -1,22 +1,25 @@ --- -title: "算法库初见" -description: "快速上手 algorithm 中的常用算法,配合 lambda 表达式实现灵活的数据处理" chapter: 11 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 快速上手 algorithm 中的常用算法,配合 lambda 表达式实现灵活的数据处理 difficulty: beginner -reading_time_minutes: 15 +order: 3 platform: host prerequisites: - - "关联容器快速上手" +- 关联容器快速上手 +reading_time_minutes: 12 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: 算法库初见 --- - # 算法库初见 前面两章,我们把 `vector` 和关联容器的基本操作都过了一遍。现在问题来了——当你需要对一堆数据做排序、查找、过滤、统计的时候,第一反应是不是写一个 for 循环? diff --git a/documents/vol1-fundamentals/ch11/04-stl-patterns.md b/documents/vol1-fundamentals/ch11/04-stl-patterns.md index 556b0c0b3..cd221564d 100644 --- a/documents/vol1-fundamentals/ch11/04-stl-patterns.md +++ b/documents/vol1-fundamentals/ch11/04-stl-patterns.md @@ -1,22 +1,25 @@ --- -title: "STL 常用模式" -description: "容器选择指南、常见陷阱和性能基础" chapter: 11 -order: 4 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 容器选择指南、常见陷阱和性能基础 difficulty: beginner -reading_time_minutes: 15 +order: 4 platform: host prerequisites: - - "算法库初见" +- 算法库初见 +reading_time_minutes: 19 tags: - - cpp-modern - - host - - beginner - - 入门 - - 基础 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- beginner +- 入门 +- 基础 +title: STL 常用模式 --- - # STL 常用模式 前面三章,我们分别搞定了 `vector`、关联容器和算法库,每一章都在各自的领域里深耕。但实际写代码的时候,问题往往不是"某个容器怎么用"或"某个算法怎么调",而是"我该选哪个容器"、"为什么我的程序跑得这么慢"、"怎么又踩到迭代器失效的坑了"。这些都是跨容器、跨算法的综合问题,需要一个系统化的视角来应对。 diff --git a/documents/vol1-fundamentals/ch12/02-new-delete.md b/documents/vol1-fundamentals/ch12/02-new-delete.md index 09a48ed88..e53cbb6db 100644 --- a/documents/vol1-fundamentals/ch12/02-new-delete.md +++ b/documents/vol1-fundamentals/ch12/02-new-delete.md @@ -1,21 +1,24 @@ --- -title: "动态内存管理" -description: "掌握 new/delete 使用与陷阱,理解 RAII 核心地位" chapter: 12 -order: 2 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 new/delete 使用与陷阱,理解 RAII 核心地位 difficulty: intermediate -reading_time_minutes: 12 +order: 2 platform: host prerequisites: - - "内存布局" +- 内存布局 +reading_time_minutes: 13 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 动态内存管理 --- - # 动态内存管理 上一章我们把程序的内存空间拆成了栈、堆、静态区、代码段四大块,搞清楚了数据"住在哪里"和"活多久"。但有一个悬念没有展开:堆上的动态内存到底怎么管?`new` 和 `delete` 背后做了什么?为什么前面几乎所有章节都在念叨"用智能指针,别裸写 `delete`"? diff --git a/documents/vol1-fundamentals/ch12/03-alignment-padding.md b/documents/vol1-fundamentals/ch12/03-alignment-padding.md index 936a78391..4e4364cef 100644 --- a/documents/vol1-fundamentals/ch12/03-alignment-padding.md +++ b/documents/vol1-fundamentals/ch12/03-alignment-padding.md @@ -1,21 +1,24 @@ --- -title: "内存对齐与填充" -description: "理解对齐规则和 sizeof 计算方法,掌握 alignas/alignof 的用法" chapter: 12 -order: 3 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解对齐规则和 sizeof 计算方法,掌握 alignas/alignof 的用法 difficulty: intermediate -reading_time_minutes: 12 +order: 3 platform: host prerequisites: - - "动态内存管理" +- 动态内存管理 +reading_time_minutes: 15 tags: - - cpp-modern - - host - - intermediate - - 进阶 -cpp_standard: [11, 14, 17, 20] +- cpp-modern +- host +- intermediate +- 进阶 +title: 内存对齐与填充 --- - # 内存对齐与填充 上一章,我们把程序的内存空间拆成了栈、堆、静态区和代码段四大区域,搞清楚了数据"住在哪里"和"活多久"。现在我们再往下一层看——即便数据都在同一块内存区域里,它也不是想怎么摆放就怎么摆放的。如果你写过一阵子 C++,大概率遇到过这样的困惑:一个结构体明明只有三个成员,`sizeof` 出来的结果却比三个成员大小之和大了不少。见了鬼了,咱们多出来的那些字节去哪了? diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/01-type-safety-and-number-concept.md b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/01-type-safety-and-number-concept.md index da2e1e302..d60ce7c73 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/01-type-safety-and-number-concept.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/01-type-safety-and-number-concept.md @@ -1,23 +1,25 @@ --- -title: "类型安全、Number 约束与边界检查" -description: "CppCon 2025 演讲笔记 —— 从隐式窄化转换到 Number 包装类型,再到 safe_int 与 checked_span" +chapter: 1 conference: cppcon conference_year: 2025 -talk_title: "Concept-based Generic Programming" -speaker: "Bjarne Stroustrup" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW" -video_youtube: "https://www.youtube.com/watch?v=VMGB75hsDQo" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +- 23 +description: CppCon 2025 演讲笔记 —— 从隐式窄化转换到 Number 包装类型,再到 safe_int 与 checked_span difficulty: intermediate -platform: host -cpp_standard: [20, 23] -chapter: 1 order: 1 +platform: host +reading_time_minutes: 45 +speaker: Bjarne Stroustrup +tags: +- cpp-modern +- host +- intermediate +talk_title: Concept-based Generic Programming +title: 类型安全、Number 约束与边界检查 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW +video_youtube: https://www.youtube.com/watch?v=VMGB75hsDQo --- - # 让我们从手动判断到隐式守护说起 :::tip diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/02-range-and-concept-composition.md b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/02-range-and-concept-composition.md index 1ca45cec9..3fef85dcd 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/02-range-and-concept-composition.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/02-range-and-concept-composition.md @@ -1,23 +1,25 @@ --- -title: "Range、迭代器与 Concept 组合" -description: "CppCon 2025 演讲笔记 —— 从迭代器对的问题到 Range 抽象,再到 Concept 组合与 requires 表达式" +chapter: 1 conference: cppcon conference_year: 2025 -talk_title: "Concept-based Generic Programming" -speaker: "Bjarne Stroustrup" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW" -video_youtube: "https://www.youtube.com/watch?v=VMGB75hsDQo" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +- 23 +description: CppCon 2025 演讲笔记 —— 从迭代器对的问题到 Range 抽象,再到 Concept 组合与 requires 表达式 difficulty: intermediate -platform: host -cpp_standard: [20, 23] -chapter: 1 order: 2 +platform: host +reading_time_minutes: 37 +speaker: Bjarne Stroustrup +tags: +- cpp-modern +- host +- intermediate +talk_title: Concept-based Generic Programming +title: Range、迭代器与 Concept 组合 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW +video_youtube: https://www.youtube.com/watch?v=VMGB75hsDQo --- - # 未检查的指针与泛型编程的边界 之前写 C++ 的时候,我遇到过一个特别典型的问题:拿到一个指针,想取它的前 10 个元素组成一个子视图,但写出来的代码怎么看怎么别扭。比如你有一个 `double*`,你想说"我要这个指针指向的前 10 个元素",但仅仅看那段代码的话,不管是你还是编译器,都没办法知道这个指针到底指向了多少个元素、10 有没有越界。这是完全未检查的。我之前一直觉得这没什么办法,指针嘛,本来就是这样。但后来我意识到,如果你的代码审查没有把这种写法标记为潜在问题,那说明审查本身也不够严格。 @@ -153,7 +155,7 @@ Concepts 是 C++20 引入的,但它的思想根源可以追溯到 Alex Stepano // 先定义我们自己的 concept:随机访问迭代器范围 // 注意:这里用标准库的 concept 来组合,不需要从零写 template -concept RandomAccessRange = +concept RandomAccessRange = std::random_access_iterator && std::sentinel_for; @@ -468,11 +470,11 @@ int main() { ```text 排序 vector: [走随机访问路径] -1 2 3 4 5 +1 2 3 4 5 排序 forward_list: [走前向迭代器路径:复制-排序-回写] -1 2 3 4 5 +1 2 3 4 5 降序排序 vector: [走随机访问路径] -5 4 3 2 1 +5 4 3 2 1 ``` 完美,两条路径各走各的,互不干扰。你注意看,谓词我给了默认值 `std::less<>`,这样常见情况就不用每次都传了,想降序的时候传个 `std::greater<>{}` 就行。这种"提供合理默认值"的习惯是我从标准库学来的,能大幅降低调用方的负担。 @@ -532,10 +534,10 @@ void my_sort(R&& r) { int main() { std::vector v{3, 1, 4, 1, 5, 9, 2, 6}; my_sort(v); // 编译通过,vector 既满足 forward_range 又满足 sortable - + // my_sort("hello"); // 编译错误,字符串不满足 sortable // 报错信息会明确告诉你:约束 'sortable_range' 未满足 - + for (int x : v) std::cout << x << ' '; // 输出:1 1 2 3 4 5 6 9 } diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/03-syntax-advanced-concepts-and-generic-philosophy.md b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/03-syntax-advanced-concepts-and-generic-philosophy.md index 678bd005e..e4c75073c 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/03-syntax-advanced-concepts-and-generic-philosophy.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/03-syntax-advanced-concepts-and-generic-philosophy.md @@ -1,23 +1,26 @@ --- -title: "语法统一、高级 Concept 与泛型哲学" -description: "CppCon 2025 演讲笔记 —— 语法统一性、SmartPtr 约束、多参数 Concept、泛型 vs OOP、迭代完善与 C++26 反射初体验" +chapter: 1 conference: cppcon conference_year: 2025 -talk_title: "Concept-based Generic Programming" -speaker: "Bjarne Stroustrup" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW" -video_youtube: "https://www.youtube.com/watch?v=VMGB75hsDQo" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +- 23 +description: CppCon 2025 演讲笔记 —— 语法统一性、SmartPtr 约束、多参数 Concept、泛型 vs OOP、迭代完善与 C++26 + 反射初体验 difficulty: intermediate -platform: host -cpp_standard: [20, 23] -chapter: 1 order: 3 +platform: host +reading_time_minutes: 44 +speaker: Bjarne Stroustrup +tags: +- cpp-modern +- host +- intermediate +talk_title: Concept-based Generic Programming +title: 语法统一、高级 Concept 与泛型哲学 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW +video_youtube: https://www.youtube.com/watch?v=VMGB75hsDQo --- - # 语法统一这件事,也许比咱们想的重要得多 我之前一直觉得,语法统一嘛,就是"写起来好看一点"的表面功夫。但如果你回头看 Simula 或者 Java,你会发现一个很别扭的设计:自定义类型必须用 `new` 来创建,但内置类型不行。在 Simula 里你甚至不能对 `int` 用 `new`。这就导致一个致命后果——你永远写不出一个真正通用的容器或者算法,因为它在语法层面就被劈成了两半。一半处理内置类型,一半处理自定义类型,两套代码,两套规则。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/04-template-compilation-and-future.md b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/04-template-compilation-and-future.md index f504eee70..ce4696222 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/04-template-compilation-and-future.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/01-concept-based-generic-programming/04-template-compilation-and-future.md @@ -1,23 +1,25 @@ --- -title: "模板编译模型与未来展望" -description: "CppCon 2025 演讲笔记 —— 模板不该被孤立编译、Concepts 作为编译期函数构建类型系统、接口继承与 Concepts 互补、未来生态建设" +chapter: 1 conference: cppcon conference_year: 2025 -talk_title: "Concept-based Generic Programming" -speaker: "Bjarne Stroustrup" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW" -video_youtube: "https://www.youtube.com/watch?v=VMGB75hsDQo" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +- 23 +description: CppCon 2025 演讲笔记 —— 模板不该被孤立编译、Concepts 作为编译期函数构建类型系统、接口继承与 Concepts 互补、未来生态建设 difficulty: intermediate -platform: host -cpp_standard: [20, 23] -chapter: 1 order: 4 +platform: host +reading_time_minutes: 28 +speaker: Bjarne Stroustrup +tags: +- cpp-modern +- host +- intermediate +talk_title: Concept-based Generic Programming +title: 模板编译模型与未来展望 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW +video_youtube: https://www.youtube.com/watch?v=VMGB75hsDQo --- - # 模板不该被孤立编译 前面聊到用 concepts 给模板加约束的时候,我脑子里一直盘旋着一个问题:如果我对 `advance` 的参数加上精确的 concept 约束,比如要求必须是 `random_access_iterator`,那 `input_iterator` 不就被拒之门外了吗?我是不是得针对不同迭代器类别写一堆重载,每个重载里用不同的方式推进?这岂不是又回到了接口不稳定的老路上——每多支持一种迭代器类型,我就得回去改 `advance` 的声明? diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/01-personal-journey-and-from-assembly-to-cpp.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/01-personal-journey-and-from-assembly-to-cpp.md index f9ebdb557..3ce223f46 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/01-personal-journey-and-from-assembly-to-cpp.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/01-personal-journey-and-from-assembly-to-cpp.md @@ -1,23 +1,25 @@ --- -title: "个人历程与从汇编到 C++ 的觉醒" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 1 +platform: host +reading_time_minutes: 36 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: 个人历程与从汇编到 C++ 的觉醒 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # 为什么 C++ 程序员应该关注汇编 很多 C++ 教程和老师都会告诉你:写 C++ 不用管底层,编译器比你聪明,你只管用模板、用智能指针、用标准库算法,剩下的交给优化器。然而在实践中,当你对着跑得慢的代码反复优化却看不到进展的时候,真正需要做的是看一看你的代码到底编译成了什么——也就是汇编输出。很多情况下,那个你以为"零成本抽象"的模板函数,编译器根本没有内联;那个你觉得"应该很快"的 lambda,在循环里被反复构造和销毁。汇编不会骗你,它就是你的代码真正变成的样子。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/02-reading-assembly-and-registers-abi.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/02-reading-assembly-and-registers-abi.md index 25441e47b..abee15cbe 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/02-reading-assembly-and-registers-abi.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/02-reading-assembly-and-registers-abi.md @@ -1,23 +1,25 @@ --- -title: "阅读汇编与寄存器 ABI" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 2 +platform: host +reading_time_minutes: 38 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: 阅读汇编与寄存器 ABI +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # 阅读汇编:从零开始建立直觉 面对满屏的 `mov`、`add`、`jmp` 搭配一堆看不懂的寄存器名,初学者的第一反应往往是关掉标签页。写模板报错的时候至少还能去 Stack Overflow 搜一下,但汇编输出就像天书一样,不知道该从哪里开始看。然而,只要借助 Compiler Explorer 做一些有针对性的实验,就会发现汇编其实可以"半读半猜"地看懂,并不需要真的会写它。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/03-compiler-explorer-and-ai-assisted.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/03-compiler-explorer-and-ai-assisted.md index 602b55b36..227f97945 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/03-compiler-explorer-and-ai-assisted.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/03-compiler-explorer-and-ai-assisted.md @@ -1,23 +1,25 @@ --- -title: "Compiler Explorer 深度使用与 AI 辅助" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 3 +platform: host +reading_time_minutes: 38 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: Compiler Explorer 深度使用与 AI 辅助 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # 用 Compiler Explorer 看汇编:从"天书"到"能看懂" 很多 C++ 开发者对阅读汇编有一种本能的抗拒,觉得那是编译器原理课或者底层工程师才需要接触的东西。然而,当模板报错信息变得难以理解、性能优化无从下手、或者 `inline` 关键字似乎不起作用的时候,学会看汇编就不再是一个可选项,而是一项必要的技能。在众多工具中,Compiler Explorer(通常简称 godbolt)是最实用的入门途径之一。本节将介绍一套从零开始阅读汇编的方法,目标是帮助读者从"完全看不懂"过渡到"能看出点门道"。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/04-stl-and-generic-programming.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/04-stl-and-generic-programming.md index 02b38ebd2..6ee379fe2 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/04-stl-and-generic-programming.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/04-stl-and-generic-programming.md @@ -1,23 +1,25 @@ --- -title: "STL 与泛型编程的本质" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 4 +platform: host +reading_time_minutes: 13 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: STL 与泛型编程的本质 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # 从 STL 的起源重新理解"泛型"到底在干什么 回顾自己的C++学习历程,笔者注意到,市场上不少的C++教程,仅仅将对 STL 的理解停留在"容器 + 算法 + 迭代器"这个三件套的层面,把它当作一个工具箱:需要什么容器就 `#include` 什么,需要排序就调 `std::sort`,用起来确实方便,也确实担当得起"标准库"这三个字(大家干活都是直接用,我猜测除非出问题了,不会有人一边写代码还要一边默念底下的模板实现!)。但很少有人想过它为什么被设计成这样。顺着 Stepanov 的历史往下挖,我们会发现一件事——STL 从一开始就不是为了"提供容器"而存在的,它的终极目标是写出一个**一劳永逸的排序算法**。 @@ -38,12 +40,12 @@ order: 4 int main() { int arr[] = {5, 3, 1, 4, 2}; - + // std::sort 不关心你传的是什么容器 // 它只关心:迭代器是不是 RandomAccessIterator(能不能做加减法、能不能解引用) // 元素能不能用 operator< 比较、能不能 swap 和移动 std::sort(std::begin(arr), std::end(arr)); - + for (int x : arr) { std::cout << x << ' '; } @@ -78,9 +80,9 @@ int main() { {"Bob", 25}, {"Charlie", 35} }; - + std::sort(std::begin(people), std::end(people)); - + for (const auto& p : people) { std::cout << p.name << ": " << p.age << '\n'; } @@ -128,7 +130,7 @@ int main() { int arr[] = {1, 2, 3, 4, 5}; // 编译器看到调用,发现已经有 int 版本的实例了,直接用 int sum = my_accumulate(arr, arr + 5, 0); - + // double arr2[] = {1.0, 2.0, 3.0}; // double sum2 = my_accumulate(arr2, arr2 + 3, 0.0); // 如果取消上面两行注释,但没有提前声明 double 版本, diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/05-boost-beman-and-standardization.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/05-boost-beman-and-standardization.md index f09a22e65..e170238b7 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/05-boost-beman-and-standardization.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/05-boost-beman-and-standardization.md @@ -1,23 +1,25 @@ --- -title: "Boost、Beman 与 C++ 标准化路径" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 5 +platform: host +reading_time_minutes: 20 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: Boost、Beman 与 C++ 标准化路径 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # Boost:原来 C++ 标准库的"后花园"长这样 学 C++ 的时候很多人都有个困惑:标准库里那些东西到底是怎么来的?是某天委员会开个会,一群大佬拍脑袋说"我们加个 `shared_ptr` 进去吧"?还是有什么更系统化的流程?看完历史资料把这条线理清楚之后,结论让人印象深刻——原来几乎所有日常使用的组件,都来自同一个地方。 @@ -193,7 +195,7 @@ std::optional extract_email(const UserInfo& user) { int main() { int input_id = 42; - + // 以前的写法:一层一层手动检查,嵌套 if,看着就累 std::optional result; auto user_opt = find_user(input_id); @@ -203,13 +205,13 @@ int main() { result = email_opt.value(); } } - + if (result) { std::cout << "邮箱: " << *result << "\n"; } else { std::cout << "无法获取邮箱\n"; } - + return 0; } ``` @@ -243,24 +245,24 @@ std::optional extract_email(const UserInfo& user) { int main() { int input_id = 42; - + // 有了 and_then 之后,链式调用,清爽多了 auto result = find_user(input_id) .and_then(extract_email); - + // transform 可以在不解包的情况下对值做变换 auto upper_result = result.transform([](const std::string& email) { std::string upper = email; for (char& c : upper) c = std::toupper(c); return upper; }); - + if (upper_result) { std::cout << "邮箱(大写): " << *upper_result << "\n"; } else { std::cout << "无法获取邮箱\n"; } - + return 0; } ``` @@ -297,17 +299,17 @@ void process(std::span data) { int main() { std::vector vec = {1, 2, 3, 4, 5}; - + // vector 直接传,完美 process(vec); - + // 取子范围也方便 process(std::span(vec).subspan(1, 3)); - + // C 数组也行 uint8_t arr[] = {10, 20, 30}; process(arr); - + return 0; } ``` diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/06-toolchain-and-project-design.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/06-toolchain-and-project-design.md index 8d22fd52f..4ae4c6096 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/06-toolchain-and-project-design.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/06-toolchain-and-project-design.md @@ -1,23 +1,25 @@ --- -title: "编译器、工具链与项目设计底线" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 6 +platform: host +reading_time_minutes: 20 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: 编译器、工具链与项目设计底线 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # C++ 的拼装工程:编译器、工具链和那些"不进标准但很好用"的库 很多程序员对 C++ 生态的理解停留在"语言本身加上标准库"的层面——写代码、编译、运行,完事了。但梳理一下整个工程流程就会发现,C++ 这个语言本身只是整个工程里的一小块。真正要把一套组件拼装成一个能跑的东西,需要的东西远比 C++ 语法多得多。今天想聊的就是这个"拼装"过程,以及支撑它的那些基础设施。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/07-wg21-standardization-and-assembly-philosophy.md b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/07-wg21-standardization-and-assembly-philosophy.md index 066b06dac..79ccbced7 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/07-wg21-standardization-and-assembly-philosophy.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/02-some-assembly-required/07-wg21-standardization-and-assembly-philosophy.md @@ -1,23 +1,25 @@ --- -title: "WG21 标准化与 x86/RISC-V 汇编哲学" -description: "CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt" +chapter: 2 conference: cppcon conference_year: 2025 -talk_title: "C++: Some Assembly Required" -speaker: "Matt Godbolt" -video_bilibili: "https://www.bilibili.com/video/BV1ptCCBKEwW?p=2" -video_youtube: "https://www.youtube.com/watch?v=zoYT7R94S3c" -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 17 +- 20 +description: 'CppCon 2025 演讲笔记 —— C++: Some Assembly Required by Matt Godbolt' difficulty: intermediate -platform: host -cpp_standard: [17, 20] -chapter: 2 order: 7 +platform: host +reading_time_minutes: 70 +speaker: Matt Godbolt +tags: +- cpp-modern +- host +- intermediate +talk_title: 'C++: Some Assembly Required' +title: WG21 标准化与 x86/RISC-V 汇编哲学 +video_bilibili: https://www.bilibili.com/video/BV1ptCCBKEwW?p=2 +video_youtube: https://www.youtube.com/watch?v=zoYT7R94S3c --- - # WG21 与 C++ 标准的组织链路 在各种技术文章和视频里,我们经常看到"WG21"这个缩写,但很少有人把这条完整的组织链路从头到尾捋清楚。实际上层级虽然多,结构本身并不复杂——我们先把这条链路理一遍,后面再看提案和标准文档的时候,至少知道这些东西是从哪来的、谁在管。 diff --git a/documents/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md b/documents/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md index 5e52fb841..7f2b9405c 100644 --- a/documents/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md +++ b/documents/vol10-open-lecture-notes/cppcon/2025/03-back-to-basics-ranges/01-from-loops-to-iterators.md @@ -1,24 +1,28 @@ --- -title: "从循环到迭代器:遍历数据的抽象之路" -description: "CppCon 2025 演讲笔记 —— Mike Shah:从 for 循环、指针遍历到迭代器抽象,补全迭代器类别体系并用 GCC 16.1.1 实测 legacy tag 与 C++20 concept 的差异" +chapter: 3 conference: cppcon conference_year: 2025 -talk_title: "Back to Basics: C++ Ranges" -speaker: "Mike Shah" -video_youtube: "https://www.youtube.com/watch?v=Q434UHWRzI0" -tags: - - cpp-modern - - host - - beginner - - Ranges - - 容器 +cpp_standard: +- 11 +- 17 +- 20 +description: CppCon 2025 演讲笔记 —— Mike Shah:从 for 循环、指针遍历到迭代器抽象,补全迭代器类别体系并用 GCC 16.1.1 + 实测 legacy tag 与 C++20 concept 的差异 difficulty: beginner -platform: host -cpp_standard: [11, 17, 20] -chapter: 3 order: 1 +platform: host +reading_time_minutes: 20 +speaker: Mike Shah +tags: +- cpp-modern +- host +- beginner +- Ranges +- 容器 +talk_title: 'Back to Basics: C++ Ranges' +title: 从循环到迭代器:遍历数据的抽象之路 +video_youtube: https://www.youtube.com/watch?v=Q434UHWRzI0 --- - # 从循环到迭代器:遍历数据的抽象之路 :::tip @@ -348,7 +352,7 @@ int sum_rangefor(const std::vector& v) 核心就一句话:**一对迭代器(一个 `begin`、一个 `end`)定义了一个 range,而 STL 算法就建立在这对迭代器之上。** -下一篇我们就把这对迭代器交给 STL 算法——看 `std::sort`、`std::partition`、`std::transform` 这些「循环的替代品」怎么用,以及它们对迭代器类别有什么硬性要求(比如 `std::sort` 为什么不能用在 `std::list` 上)。那里还有几个迭代器的经典陷阱等着我们:迭代器失效、配错 `begin`/`end`、参数顺序写反。如果你想先复习一下容器本身的内存布局,vol3 的 [span:不拥有数据的视图](../../../../vol3-standard-library/02-span.md) 和容器相关文章是很好的前置阅读。 +下一篇我们就把这对迭代器交给 STL 算法——看 `std::sort`、`std::partition`、`std::transform` 这些「循环的替代品」怎么用,以及它们对迭代器类别有什么硬性要求(比如 `std::sort` 为什么不能用在 `std::list` 上)。那里还有几个迭代器的经典陷阱等着我们:迭代器失效、配错 `begin`/`end`、参数顺序写反。如果你想先复习一下容器本身的内存布局,vol3 的 [span:不拥有数据的视图](../../../../vol3-standard-library/08-span.md) 和容器相关文章是很好的前置阅读。 (t)` —— 要么语义不明,要么写法丑陋。C++11 引入了 `std::tie` 来缓解这个问题,但老实说,那语法也不算优雅:你得先声明好所有变量,再用 `tie` 往里塞。有没有那种,跟Python返回多个值的拆分写法一样爽的feature呢? 真有了孩子们! diff --git a/documents/vol2-modern-features/ch05-structured-bindings/02-init-statements.md b/documents/vol2-modern-features/ch05-structured-bindings/02-init-statements.md index 80d3a4647..45eeb461a 100644 --- a/documents/vol2-modern-features/ch05-structured-bindings/02-init-statements.md +++ b/documents/vol2-modern-features/ch05-structured-bindings/02-init-statements.md @@ -1,22 +1,22 @@ --- -title: "if/switch 初始化器:缩小变量作用域" -description: "C++17 的 if 和 switch 初始化器,让变量生命周期恰到好处" chapter: 5 -order: 2 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: C++17 的 if 和 switch 初始化器,让变量生命周期恰到好处 difficulty: intermediate +order: 2 platform: host -cpp_standard: [17] -reading_time_minutes: 12 prerequisites: - - "Chapter 5: 结构化绑定" +- 'Chapter 5: 结构化绑定' +reading_time_minutes: 9 related: - - "RAII 深入理解" +- RAII 深入理解 +tags: +- host +- cpp-modern +- intermediate +title: if/switch 初始化器:缩小变量作用域 --- - # if/switch 初始化器:缩小变量作用域 笔者在review代码的时候经常看到这种模式:先声明一个变量,用它做判断,然后整个函数剩余部分都能看到这个变量——即使它只在 `if` 分支里有意义。这种"变量泄漏到外层作用域"的问题在 C++ 里由来已久,但 C++17 终于给了我们一个优雅的解决方案:if 和 switch 的初始化语句。 diff --git a/documents/vol2-modern-features/ch06-auto-decltype/01-auto-deep-dive.md b/documents/vol2-modern-features/ch06-auto-decltype/01-auto-deep-dive.md index 30f2f6e8a..fd32ff909 100644 --- a/documents/vol2-modern-features/ch06-auto-decltype/01-auto-deep-dive.md +++ b/documents/vol2-modern-features/ch06-auto-decltype/01-auto-deep-dive.md @@ -1,25 +1,27 @@ --- -title: "auto 推导深入:不只是偷懒" -description: "理解 auto 的完整推导规则、常见陷阱与最佳实践" chapter: 6 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 类型别名 - - 类型安全 +cpp_standard: +- 11 +- 14 +- 17 +description: 理解 auto 的完整推导规则、常见陷阱与最佳实践 difficulty: intermediate +order: 1 platform: host -cpp_standard: [11, 14, 17] -reading_time_minutes: 18 prerequisites: - - "Chapter 0: 右值引用" +- 'Chapter 0: 右值引用' +reading_time_minutes: 11 related: - - "decltype 与返回类型推导" - - "类模板参数推导" +- decltype 与返回类型推导 +- 类模板参数推导 +tags: +- host +- cpp-modern +- intermediate +- 类型别名 +- 类型安全 +title: auto 推导深入:不只是偷懒 --- - # auto 推导深入:不只是偷懒 笔者每次看到有人把 `auto` 理解成"让编译器猜类型"就想纠正一下。`auto` 的推导规则其实是完全确定的,和模板参数推导遵循同一套机制。它不是魔法,更不是偷懒——在很多场景下,用 `auto` 比手写类型更安全,因为当你改了某个函数的返回类型时,所有用 `auto` 接收的地方会自动跟着变,不会出现忘记改的情况。 diff --git a/documents/vol2-modern-features/ch06-auto-decltype/02-decltype.md b/documents/vol2-modern-features/ch06-auto-decltype/02-decltype.md index 5aa81ce7f..a165a54b2 100644 --- a/documents/vol2-modern-features/ch06-auto-decltype/02-decltype.md +++ b/documents/vol2-modern-features/ch06-auto-decltype/02-decltype.md @@ -1,22 +1,24 @@ --- -title: "decltype 与返回类型推导" -description: "decltype 的推导规则、decltype(auto) 与尾置返回类型" chapter: 6 -order: 2 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 11 +- 14 +- 17 +description: decltype 的推导规则、decltype(auto) 与尾置返回类型 difficulty: intermediate +order: 2 platform: host -cpp_standard: [11, 14, 17] -reading_time_minutes: 15 prerequisites: - - "Chapter 6: auto 推导深入" +- 'Chapter 6: auto 推导深入' +reading_time_minutes: 10 related: - - "类模板参数推导" +- 类模板参数推导 +tags: +- host +- cpp-modern +- intermediate +title: decltype 与返回类型推导 --- - # decltype 与返回类型推导 上一章我们详细讲了 `auto` 的推导规则——默认丢弃引用和顶层 const。但有些时候我们需要的是"原封不动地保留表达式的类型",包括引用和 const。这就是 `decltype` 的领域。 diff --git a/documents/vol2-modern-features/ch06-auto-decltype/03-ctad.md b/documents/vol2-modern-features/ch06-auto-decltype/03-ctad.md index 293a8e592..8076f32f7 100644 --- a/documents/vol2-modern-features/ch06-auto-decltype/03-ctad.md +++ b/documents/vol2-modern-features/ch06-auto-decltype/03-ctad.md @@ -1,23 +1,24 @@ --- -title: "类模板参数推导 (CTAD)" -description: "C++17 的 CTAD 机制与自定义推导指引" chapter: 6 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - 泛型 +cpp_standard: +- 17 +- 20 +description: C++17 的 CTAD 机制与自定义推导指引 difficulty: intermediate +order: 3 platform: host -cpp_standard: [17, 20] -reading_time_minutes: 15 prerequisites: - - "Chapter 6: auto 推导深入" +- 'Chapter 6: auto 推导深入' +reading_time_minutes: 13 related: - - "decltype 与返回类型推导" +- decltype 与返回类型推导 +tags: +- host +- cpp-modern +- intermediate +- 泛型 +title: 类模板参数推导 (CTAD) --- - # 类模板参数推导 (CTAD) 在 C++17 之前,每次实例化类模板都得把模板参数写全。哪怕编译器完全能从构造函数的参数推导出模板参数,你也得老老实实写一遍: diff --git a/documents/vol2-modern-features/ch07-attributes/01-standard-attributes.md b/documents/vol2-modern-features/ch07-attributes/01-standard-attributes.md index a5a2c43f3..517fb9f74 100644 --- a/documents/vol2-modern-features/ch07-attributes/01-standard-attributes.md +++ b/documents/vol2-modern-features/ch07-attributes/01-standard-attributes.md @@ -1,22 +1,24 @@ --- -title: "标准属性详解:让编译器成为你的代码审查员" -description: "C++11-17 标准属性的语义、用法与最佳实践" chapter: 7 -order: 1 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 11 +- 14 +- 17 +description: C++11-17 标准属性的语义、用法与最佳实践 difficulty: intermediate +order: 1 platform: host -cpp_standard: [11, 14, 17] -reading_time_minutes: 18 prerequisites: - - "Chapter 1: RAII 深入理解" +- 'Chapter 1: RAII 深入理解' +reading_time_minutes: 12 related: - - "C++20-23 新属性" +- C++20-23 新属性 +tags: +- host +- cpp-modern +- intermediate +title: 标准属性详解:让编译器成为你的代码审查员 --- - # 标准属性详解:让编译器成为你的代码审查员 笔者在写代码的时候,经常遇到几种令人头大的情况:调用了一个返回错误码的函数但忘了检查,编译器一声不吭就放过了;有个参数在某个编译配置下用不到,编译器满屏警告未使用变量;想标记某个 API 过时了,但只能靠文档或注释提醒调用方。C++11 开始引入、并在后续版本逐步扩展的标准属性语法 `[[attribute]]` 就是来解决这些问题的——用标准化的方式给编译器传递额外信息,让它帮我们做静态检查。 diff --git a/documents/vol2-modern-features/ch07-attributes/02-modern-attributes.md b/documents/vol2-modern-features/ch07-attributes/02-modern-attributes.md index a5ec5eb1a..6aafdb3f6 100644 --- a/documents/vol2-modern-features/ch07-attributes/02-modern-attributes.md +++ b/documents/vol2-modern-features/ch07-attributes/02-modern-attributes.md @@ -1,22 +1,23 @@ --- -title: "C++20-23 新属性:性能导向的编译器提示" -description: "[[likely]]/[[unlikely]]、[[no_unique_address]]、[[assume]] 等新属性" chapter: 7 -order: 2 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 20 +- 23 +description: '[[likely]]/[[unlikely]]、[[no_unique_address]]、[[assume]] 等新属性' difficulty: intermediate +order: 2 platform: host -cpp_standard: [20, 23] -reading_time_minutes: 15 prerequisites: - - "Chapter 7: 标准属性详解" +- 'Chapter 7: 标准属性详解' +reading_time_minutes: 14 related: - - "constexpr 构造函数与字面类型" +- constexpr 构造函数与字面类型 +tags: +- host +- cpp-modern +- intermediate +title: C++20-23 新属性:性能导向的编译器提示 --- - # C++20-23 新属性:性能导向的编译器提示 上一章我们看了 C++11-17 的标准属性,它们主要解决"代码正确性"的问题——强制检查返回值、消除警告、标记废弃 API。C++20 和 C++23 新增的属性则换了一个方向:它们更关注性能,给编译器提供优化提示。`[[likely]]` 和 `[[unlikely]]` 帮编译器做分支预测优化(啊哈,我记得我第一次接触是在看GNU C特性的代码),`[[no_unique_address]]` 节省内存布局中的冗余空间,`[[assume]]` 让编译器基于假设做更激进的优化。 @@ -156,7 +157,7 @@ struct Container { struct [[gnu::packed]] PackedContainer { // offset 0: e (1 字节) // offset 1~4: x (4 字节) - Empty e; + Empty e; int x; }; diff --git a/documents/vol2-modern-features/ch08-string-view/01-string-view-internals.md b/documents/vol2-modern-features/ch08-string-view/01-string-view-internals.md index 96583d7be..6564e4fa5 100644 --- a/documents/vol2-modern-features/ch08-string-view/01-string-view-internals.md +++ b/documents/vol2-modern-features/ch08-string-view/01-string-view-internals.md @@ -1,23 +1,23 @@ --- -title: "string_view 内部原理:非拥有字符串视图" -description: "理解 string_view 的实现机制、与 SSO 的对比和构造来源" chapter: 8 -order: 1 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: 理解 string_view 的实现机制、与 SSO 的对比和构造来源 difficulty: intermediate +order: 1 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 0: 右值引用" +- 'Chapter 0: 右值引用' +reading_time_minutes: 18 related: - - "string_view 性能分析" - - "string_view 陷阱与最佳实践" +- string_view 性能分析 +- string_view 陷阱与最佳实践 +tags: +- host +- cpp-modern +- intermediate +title: string_view 内部原理:非拥有字符串视图 --- - # string_view 内部原理:非拥有字符串视图 笔者最近在写一个 IniParser 项目的时候,跟字符串打交道打得快要吐了——split、trim、substr,各种操作满天飞。每次用 `std::string` 做子串操作都意味着一次堆分配,解析一个配置文件下来,堆上的碎片比笔者的桌面还乱。后来笔者认真研究了一下 `std::string_view`,才发现 C++17 给我们准备了一个这么好用的工具。不过用好它的前提是真正理解它的内部机制,否则很容易踩到生命周期的坑——这个我们留到下一篇陷阱篇再细说。 diff --git a/documents/vol2-modern-features/ch08-string-view/02-string-view-performance.md b/documents/vol2-modern-features/ch08-string-view/02-string-view-performance.md index 58793f117..05f316c70 100644 --- a/documents/vol2-modern-features/ch08-string-view/02-string-view-performance.md +++ b/documents/vol2-modern-features/ch08-string-view/02-string-view-performance.md @@ -1,22 +1,22 @@ --- -title: "string_view 性能分析" -description: "基准测试 string_view 替代 const string& 的性能收益" chapter: 8 -order: 2 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: 基准测试 string_view 替代 const string& 的性能收益 difficulty: intermediate +order: 2 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 8: string_view 内部原理" +- 'Chapter 8: string_view 内部原理' +reading_time_minutes: 13 related: - - "string_view 陷阱与最佳实践" +- string_view 陷阱与最佳实践 +tags: +- host +- cpp-modern +- intermediate +title: string_view 性能分析 --- - # string_view 性能分析 上一篇我们深入了 `string_view` 的内部原理,知道了它是"指针 + 长度"的非拥有视图。这一篇我们用数据说话——`string_view` 到底比 `const std::string&` 快多少?在什么场景下收益最大?有没有反而更慢的情况? diff --git a/documents/vol2-modern-features/ch08-string-view/03-string-view-pitfalls.md b/documents/vol2-modern-features/ch08-string-view/03-string-view-pitfalls.md index 0cafced6f..7eb4c06d1 100644 --- a/documents/vol2-modern-features/ch08-string-view/03-string-view-pitfalls.md +++ b/documents/vol2-modern-features/ch08-string-view/03-string-view-pitfalls.md @@ -1,22 +1,22 @@ --- -title: "string_view 陷阱与最佳实践" -description: "悬垂引用、null 终止、隐式转换——string_view 的常见坑与规避方法" chapter: 8 -order: 3 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: 悬垂引用、null 终止、隐式转换——string_view 的常见坑与规避方法 difficulty: intermediate +order: 3 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 8: string_view 内部原理" +- 'Chapter 8: string_view 内部原理' +reading_time_minutes: 14 related: - - "string_view 性能分析" +- string_view 性能分析 +tags: +- host +- cpp-modern +- intermediate +title: string_view 陷阱与最佳实践 --- - # string_view 陷阱与最佳实践 前面两篇我们讲了 `string_view` 的内部原理和性能优势,看起来它简直是个完美的工具——轻量、快速、零分配。但笔者必须在这里泼一盆冷水:`string_view` 是笔者用过的 C++ 特性中,最容易写出未定义行为的工具之一。原因很简单:它不拥有数据。只要你忘了这一点,悬垂引用、野指针、乱码、甚至安全漏洞都可能等着你。 diff --git a/documents/vol2-modern-features/ch09-filesystem/01-filesystem-path.md b/documents/vol2-modern-features/ch09-filesystem/01-filesystem-path.md index eb96011f5..ee2971693 100644 --- a/documents/vol2-modern-features/ch09-filesystem/01-filesystem-path.md +++ b/documents/vol2-modern-features/ch09-filesystem/01-filesystem-path.md @@ -1,22 +1,22 @@ --- -title: "path 操作:跨平台路径处理" -description: "用 std::filesystem::path 统一处理跨平台路径" chapter: 9 -order: 1 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: 用 std::filesystem::path 统一处理跨平台路径 difficulty: intermediate +order: 1 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 1: RAII 深入理解" +- 'Chapter 1: RAII 深入理解' +reading_time_minutes: 14 related: - - "文件与目录操作" +- 文件与目录操作 +tags: +- host +- cpp-modern +- intermediate +title: path 操作:跨平台路径处理 --- - # path 操作:跨平台路径处理 笔者之前写跨平台代码的时候,最头疼的就是路径处理。Windows 用反斜杠 `\`,Linux 和 macOS 用正斜杠 `/`,路径分隔符不一样就算了,绝对路径的表示方式也不同(`C:\Users\...` vs `/home/...`),更别提 Unicode 文件名、符号链接这些高级话题。以前只能靠一堆 `#ifdef _WIN32` 加上字符串拼接来凑合,代码写得自己都不想看。 @@ -362,7 +362,7 @@ std::vector find_by_extension(const fs::path& dir, } std::string lower_ext; - std::transform(ext.begin(), ext.end(), std::back_inserter(lower_ext), ::tolower); + std::transform(ext.begin(), ext.end(), std::back_inserter(lower_ext), ::tolower); for (const auto& entry : fs::directory_iterator(dir)) { if (entry.is_regular_file()) { auto path_ext = entry.path().extension().string(); diff --git a/documents/vol2-modern-features/ch09-filesystem/02-filesystem-ops.md b/documents/vol2-modern-features/ch09-filesystem/02-filesystem-ops.md index aa77e6e91..e16bb7c66 100644 --- a/documents/vol2-modern-features/ch09-filesystem/02-filesystem-ops.md +++ b/documents/vol2-modern-features/ch09-filesystem/02-filesystem-ops.md @@ -1,22 +1,22 @@ --- -title: "文件与目录操作" -description: "exists、copy、move、remove、权限与空间查询" chapter: 9 -order: 2 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: exists、copy、move、remove、权限与空间查询 difficulty: intermediate +order: 2 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 9: path 操作" +- 'Chapter 9: path 操作' +reading_time_minutes: 16 related: - - "目录遍历与搜索" +- 目录遍历与搜索 +tags: +- host +- cpp-modern +- intermediate +title: 文件与目录操作 --- - # 文件与目录操作 上一篇我们学会了用 `std::filesystem::path` 处理路径的语法问题——构造、分解、修改、比较,全是纯计算,不碰磁盘。这一篇我们开始动真格的:用 `` 库直接操作文件系统——检查文件是否存在、创建目录、复制文件、删除文件、查询权限和磁盘空间。 @@ -215,7 +215,7 @@ fs::path dst = "/backup/important_config.yaml"; std::error_code ec; fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec); -// 可能性1. 如果 dst 已经存在, 复制过程中内容可能会被逐步覆盖, +// 可能性1. 如果 dst 已经存在, 复制过程中内容可能会被逐步覆盖, // 从而导致其他进程看到一个被部分复制的文件 // 可能性2. 如果复制中途停电宕机, dst文件可能处于不完整甚至损坏的状态 if (ec) { diff --git a/documents/vol2-modern-features/ch09-filesystem/03-directory-iteration.md b/documents/vol2-modern-features/ch09-filesystem/03-directory-iteration.md index ae0b586c0..cf941ed90 100644 --- a/documents/vol2-modern-features/ch09-filesystem/03-directory-iteration.md +++ b/documents/vol2-modern-features/ch09-filesystem/03-directory-iteration.md @@ -1,23 +1,23 @@ --- -title: "目录遍历与搜索" -description: "directory_iterator 与 recursive_directory_iterator 的用法与性能" chapter: 9 -order: 3 -tags: - - host - - cpp-modern - - intermediate +cpp_standard: +- 17 +description: directory_iterator 与 recursive_directory_iterator 的用法与性能 difficulty: intermediate +order: 3 platform: host -cpp_standard: [17] -reading_time_minutes: 15 prerequisites: - - "Chapter 9: path 操作" - - "Chapter 9: 文件与目录操作" +- 'Chapter 9: path 操作' +- 'Chapter 9: 文件与目录操作' +reading_time_minutes: 13 related: - - "Lambda 基础" +- Lambda 基础 +tags: +- host +- cpp-modern +- intermediate +title: 目录遍历与搜索 --- - # 目录遍历与搜索 前两篇我们学会了用 `path` 处理路径、用文件操作函数管理文件和目录。但在实际项目中,最常见的需求其实是"在某个目录下找到我想要的文件"。比如:收集所有 `.cpp` 文件送给编译器,在资源目录里找到所有纹理图片,或者统计项目代码的总行数。 diff --git a/documents/vol2-modern-features/ch10-error-handling/01-error-handling-evolution.md b/documents/vol2-modern-features/ch10-error-handling/01-error-handling-evolution.md index 3af43792f..57c0ce7fe 100644 --- a/documents/vol2-modern-features/ch10-error-handling/01-error-handling-evolution.md +++ b/documents/vol2-modern-features/ch10-error-handling/01-error-handling-evolution.md @@ -1,25 +1,29 @@ --- -title: "错误处理的演进:从错误码到类型安全" -description: "错误码、异常、optional、expected——错误处理方案的演进与选择" chapter: 10 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 类型安全 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 错误码、异常、optional、expected——错误处理方案的演进与选择 difficulty: intermediate +order: 1 platform: host -cpp_standard: [11, 14, 17, 20, 23] -reading_time_minutes: 18 prerequisites: - - "Chapter 4: std::optional" - - "Chapter 4: std::variant" +- 'Chapter 4: std::optional' +- 'Chapter 4: std::variant' +reading_time_minutes: 13 related: - - "optional 用于错误处理" - - "std::expected" +- optional 用于错误处理 +- std::expected +tags: +- host +- cpp-modern +- intermediate +- 类型安全 +title: 错误处理的演进:从错误码到类型安全 --- - # 错误处理的演进:从错误码到类型安全 笔者写 C++ 这些年,感受最深的一件事就是:**错误处理永远是项目中最难做好的部分**。不是因为它复杂——恰恰是因为它看起来太简单了。很多人觉得 `if (ret != 0)` 或者 `try { ... } catch (...)` 就够了,但真正到了维护阶段,才发现到处都是没处理的错误、被吞掉的异常、以及根本不知道为什么失败的函数调用。 diff --git a/documents/vol2-modern-features/ch10-error-handling/02-optional-error.md b/documents/vol2-modern-features/ch10-error-handling/02-optional-error.md index d9f615869..d3ce9eb53 100644 --- a/documents/vol2-modern-features/ch10-error-handling/02-optional-error.md +++ b/documents/vol2-modern-features/ch10-error-handling/02-optional-error.md @@ -1,25 +1,26 @@ --- -title: "optional 用于错误处理" -description: "用 std::optional 表示'可能失败的操作',替代错误码和异常" chapter: 10 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - optional - - 类型安全 +cpp_standard: +- 17 +- 23 +description: 用 std::optional 表示'可能失败的操作',替代错误码和异常 difficulty: intermediate +order: 2 platform: host -cpp_standard: [17, 23] -reading_time_minutes: 15 prerequisites: - - "Chapter 10: 错误处理的演进" - - "Chapter 4: std::optional" +- 'Chapter 10: 错误处理的演进' +- 'Chapter 4: std::optional' +reading_time_minutes: 10 related: - - "std::expected" +- std::expected +tags: +- host +- cpp-modern +- intermediate +- optional +- 类型安全 +title: optional 用于错误处理 --- - # optional 用于错误处理 在上一篇里我们梳理了 C++ 错误处理的演进路线,最后提到 `std::optional` 可以用于表达"可能失败的操作"。这一篇我们就来深入看看,`optional` 在错误处理场景下到底好不好用、该怎么用、以及什么时候不该用它。 diff --git a/documents/vol2-modern-features/ch10-error-handling/03-expected-error.md b/documents/vol2-modern-features/ch10-error-handling/03-expected-error.md index 4690b3422..3b7a84446 100644 --- a/documents/vol2-modern-features/ch10-error-handling/03-expected-error.md +++ b/documents/vol2-modern-features/ch10-error-handling/03-expected-error.md @@ -1,25 +1,25 @@ --- -title: "std::expected:类型安全的错误传播" -description: "C++23 的 expected 类型与 monadic 操作,实现优雅的错误传播链" chapter: 10 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - expected - - 类型安全 +cpp_standard: +- 23 +description: C++23 的 expected 类型与 monadic 操作,实现优雅的错误传播链 difficulty: intermediate +order: 3 platform: host -cpp_standard: [23] -reading_time_minutes: 18 prerequisites: - - "Chapter 10: 错误处理的演进" - - "Chapter 10: optional 用于错误处理" +- 'Chapter 10: 错误处理的演进' +- 'Chapter 10: optional 用于错误处理' +reading_time_minutes: 11 related: - - "错误处理模式总结" +- 错误处理模式总结 +tags: +- host +- cpp-modern +- intermediate +- expected +- 类型安全 +title: std::expected:类型安全的错误传播 --- - # std::expected:类型安全的错误传播 上一篇我们聊了 `std::optional` 在错误处理中的应用,也指出了它的局限——不能携带错误信息。当你需要知道"为什么失败"的时候,`optional` 就力不从心了。C++23 引入的 `std::expected` 正是为了填补这个空白:它既告诉你"有没有值",也告诉你"没有值的原因是什么"。 diff --git a/documents/vol2-modern-features/ch10-error-handling/04-error-patterns.md b/documents/vol2-modern-features/ch10-error-handling/04-error-patterns.md index 62bf75a4a..9474a547e 100644 --- a/documents/vol2-modern-features/ch10-error-handling/04-error-patterns.md +++ b/documents/vol2-modern-features/ch10-error-handling/04-error-patterns.md @@ -1,25 +1,29 @@ --- -title: "错误处理模式总结:选择指南与最佳实践" -description: "综合对比所有错误处理方案,提供场景化选择指南" chapter: 10 -order: 4 -tags: - - host - - cpp-modern - - intermediate - - 类型安全 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +- 23 +description: 综合对比所有错误处理方案,提供场景化选择指南 difficulty: intermediate +order: 4 platform: host -cpp_standard: [11, 14, 17, 20, 23] -reading_time_minutes: 20 prerequisites: - - "Chapter 10: 错误处理的演进" - - "Chapter 10: optional 用于错误处理" - - "Chapter 10: std::expected" +- 'Chapter 10: 错误处理的演进' +- 'Chapter 10: optional 用于错误处理' +- 'Chapter 10: std::expected' +reading_time_minutes: 13 related: - - "RAII 深入理解" +- RAII 深入理解 +tags: +- host +- cpp-modern +- intermediate +- 类型安全 +title: 错误处理模式总结:选择指南与最佳实践 --- - # 错误处理模式总结:选择指南与最佳实践 经过前三篇文章的铺垫,我们分别讨论了错误码、异常、`optional` 和 `expected` 的优缺点。这一篇是整个错误处理主题的收尾——笔者要把所有方案放在一起做一个综合对比,然后给出一个实用的选择指南,以及一些从踩坑中总结出来的最佳实践。 diff --git a/documents/vol2-modern-features/ch11-user-defined-literals/01-udl-basics.md b/documents/vol2-modern-features/ch11-user-defined-literals/01-udl-basics.md index 0a6fe5b64..cd8088a83 100644 --- a/documents/vol2-modern-features/ch11-user-defined-literals/01-udl-basics.md +++ b/documents/vol2-modern-features/ch11-user-defined-literals/01-udl-basics.md @@ -1,23 +1,25 @@ --- -title: "用户自定义字面量基础" -description: "operator\"\" 的原始/cooked 形式与标准库字面量" chapter: 11 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 字面量 +cpp_standard: +- 11 +- 14 +- 17 +description: operator"" 的原始/cooked 形式与标准库字面量 difficulty: intermediate +order: 1 platform: host -cpp_standard: [11, 14, 17] -reading_time_minutes: 15 prerequisites: - - "Chapter 2: constexpr 基础" +- 'Chapter 2: constexpr 基础' +reading_time_minutes: 10 related: - - "UDL 实战" +- UDL 实战 +tags: +- host +- cpp-modern +- intermediate +- 字面量 +title: 用户自定义字面量基础 --- - # 用户自定义字面量基础 笔者在写嵌入式代码的时候,经常遇到这种让人难受的场景:`TIM1->ARR = (1000 - 1)` 里面的 1000 是毫秒还是微秒?`USART1->BRR = 0x271` 到底是 9600 还是 115200?`#define BUFFER_SIZE 1024` 是字节还是字?这些"魔数"不仅难以理解,还容易出错——更糟糕的是,不同单位之间的转换完全依赖程序员手动计算,稍有不慎就会出问题。 diff --git a/documents/vol2-modern-features/ch11-user-defined-literals/02-udl-practice.md b/documents/vol2-modern-features/ch11-user-defined-literals/02-udl-practice.md index 629c15ea1..3aaeb7e29 100644 --- a/documents/vol2-modern-features/ch11-user-defined-literals/02-udl-practice.md +++ b/documents/vol2-modern-features/ch11-user-defined-literals/02-udl-practice.md @@ -1,25 +1,26 @@ --- -title: "UDL 实战:类型安全的单位系统" -description: "用用户自定义字面量实现类型安全的物理单位系统" chapter: 11 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - 字面量 - - 类型安全 +cpp_standard: +- 14 +- 17 +description: 用用户自定义字面量实现类型安全的物理单位系统 difficulty: intermediate +order: 2 platform: host -cpp_standard: [14, 17] -reading_time_minutes: 15 prerequisites: - - "Chapter 11: 用户自定义字面量基础" - - "Chapter 4: 强类型 typedef" +- 'Chapter 11: 用户自定义字面量基础' +- 'Chapter 4: 强类型 typedef' +reading_time_minutes: 11 related: - - "constexpr 基础" +- constexpr 基础 +tags: +- host +- cpp-modern +- intermediate +- 字面量 +- 类型安全 +title: UDL 实战:类型安全的单位系统 --- - # UDL 实战:类型安全的单位系统 上一篇我们学了用户自定义字面量的基础语法——`operator""` 的各种形式、标准库字面量、命名规则。这一篇我们要把这些知识用起来,构建一个真正实用的**类型安全单位系统**。 diff --git a/documents/vol3-standard-library/01-array.md b/documents/vol3-standard-library/01-array.md deleted file mode 100644 index 5402e93ab..000000000 --- a/documents/vol3-standard-library/01-array.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -chapter: 7 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: std::array容器详解 -difficulty: intermediate -order: 1 -platform: host -prerequisites: -- 'Chapter 6: RAII与智能指针' -reading_time_minutes: 6 -tags: -- cpp-modern -- host -- intermediate -title: std::array 固定大小容器 ---- -# 嵌入式现代C++教程——std::array:编译期固定大小数组 - -你写嵌入式代码时,堆(heap)常常像个不可靠的室友:随时可能把屋子掀翻。`std::array` 就像那位稳重但不多话的朋友——编译期确定大小、占栈或静态存储、没有动态分配,性能可预测,语义清楚。我们的一个重点就是——他跟传统的C风格数组比起来,怎么样的事情。 - ------- - -## 什么是 `std::array` - -`std::array` 是一个封装了 C 风格数组的轻量类模板:大小 `N` 在编译期就确定,提供 STL 风格的接口(`.size()`、`.begin()`、`.data()`、`operator[]`、迭代器等),且通常不会比原始数组多多少运行开销。 - ------- - -## 为什么在嵌入式喜欢它 - -- **零动态分配**:没有 `new` / `malloc`,适合无堆或受限内存环境。 -- **可预测内存布局**:编译期大小、连续存储,方便用于 DMA、裸指针接口。 -- **STL 友好**:可以直接传给算法(`std::sort`、`std::fill`)和容器适配器。 -- **constexpr 支持**:可以用作编译期查表或常量数据。 -- **类型安全与自文档化**:`std::array` 明确表达意图,比 `uint8_t buf[128]` 更现代。 - ------- - -## 基本用法(代码示例) - -```cpp -#include -#include -#include -#include - -int main() { - std::array buf{}; // value-initialized -> all zeros - buf[0] = 0xAA; - buf.at(1) = 0x55; // .at 会做边界检查(抛异常) - - // 兼容 STL 算法 - std::fill(buf.begin(), buf.end(), 0xFF); - - // 传给 C API(不会隐式退化):使用 data() - // c_function(buf.data(), buf.size()); - - for (auto b : buf) std::cout << int(b) << ' '; - std::cout << '\n'; -} - -``` - -小提醒:`.at()` 在异常被禁用或不可用的裸机环境下不适合;用 `operator[]` 并保持索引正确。 - ------- - -## 与 C 数组、`std::vector` 的比较 - -- 和 **C 数组**:`std::array` 是包起来的类,支持 `.size()`、迭代器、`std::get`、结构化绑定,且能作为对象被拷贝/赋值。底层仍是连续内存。 -- 和 **std::vector**:`vector` 可动态调整大小(需堆),`std::array` 无堆、大小固定、开销更小、语义更明确,嵌入式通常更倾向 `std::array`。 - ------- - -## 常见技巧和细节(嵌入式角度) - -### 1. 放静态区还是栈上? - -- 小数组(几十、几百字节)可放在栈上。注意任务/ISR 的栈深度限制。 -- 较大数组应放为 `static` 或放在 `.bss`,或放入只读闪存(`constexpr` 数据)以节省 RAM。 - -示例: - -```cpp -static std::array big_buf; // 在 .bss,程序启动后分配 - -``` - -### 2. 用于 DMA / 外设 - -因为 `std::array` 保证连续内存,你可以安全地传 `arr.data()` 给 DMA 或 HAL。但确保元素类型是 **可复制且没有需要特殊构造的复杂类型**(一般使用 POD 或 trivial 类型)。 - -### 3. 编译期表与 `constexpr` - -`std::array` 可用于编译期常量查表(免运行时初始化): - -```cpp -#include - -constexpr std::array make_table() { - return {0, 1, 4, 9, 16}; -} - -constexpr auto table = make_table(); // 存在于只读段,可放进 flash -static_assert(table[3] == 9); - -``` - -如果你需要在编译期生成一个更复杂的表,可以配合 `std::index_sequence` 做元编程(不赘述复杂实现,这里先给出思路:用 `index_sequence` 展开索引并在 constexpr 函数中产生元素`)。 - -### 4. 结构化绑定与 `std::get` - -`std::array` 支持 `std::get<0>(arr)` 和结构化绑定(C++17): - -```cpp -std::array a = {1,2,3}; -auto [x,y,z] = a; // nice for small fixed tuples - -``` - -### 5. 避免退化为指针的陷阱 - -C 风格数组在传参时会退化为指针,而 `std::array` 不会,你必须明确传 `.data()` 或 `.size()`: - -```cpp -void c_api(uint8_t* p, size_t n); -std::array arr; -c_api(arr.data(), arr.size()); - -``` - -### 6. 与裸机异常策略的兼容性 - -某些嵌入式编译链把异常支持关掉,这会影响 `.at()`(抛异常)的使用。建议在无异常环境下只用 `operator[]` 并在编译期/开发期做边界检查工具。 - ------- - -## 高级话题:当元素不是 POD 时 - -`std::array` 的元素可以是任意类型 `T`。但在嵌入式里常见注意点: - -- 如果 `T` 有复杂构造/析构,静态初始化(尤其零初始化)行为会不同,要保证构造成本被接受。 -- 对于需要通过 DMA 读写的缓冲区,`T` 应该是 trivially copyable。 - -## 在线运行 - -在线体验 std::array 的基本用法、constexpr 查表与结构化绑定: - - - -## 可以一试——把 `std::array` 用作编译期 CRC 表 - -```cpp -#include -#include - -constexpr std::array make_crc_table() { - std::array t{}; - for (size_t i = 0; i < 256; ++i) { - uint32_t crc = static_cast(i); - for (int j = 0; j < 8; ++j) - crc = (crc & 1) ? (0xEDB88320u ^ (crc >> 1)) : (crc >> 1); - t[i] = crc; - } - return t; -} - -constexpr auto crc_table = make_crc_table(); // 编译期计算,放到只读段(若编译器支持) - -``` - -在支持的工具链上,这样可以把查表数据放进 flash,节省 RAM。 diff --git a/documents/vol3-standard-library/01-container-selection-guide.md b/documents/vol3-standard-library/01-container-selection-guide.md new file mode 100644 index 000000000..fd259c39b --- /dev/null +++ b/documents/vol3-standard-library/01-container-selection-guide.md @@ -0,0 +1,169 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 17 +- 20 +- 23 +description: 把 vol3 讲过的顺序容器与关联容器串成一张决策地图:按操作复杂度、内存局部性、迭代器失效规则三条线,加上一棵选择决策树,说清选错容器会踩的坑 +difficulty: intermediate +order: 1 +platform: host +prerequisites: +- array:编译期固定大小的聚合容器 +reading_time_minutes: 11 +related: +- vector 深入:三指针、扩容与迭代器失效 +- deque、list 与 forward_list:vector 之外的三个选择 +- map 与 set 深入 +- unordered_map 与 set 深入 +- span:非拥有的连续视图 +tags: +- host +- cpp-modern +- intermediate +- 容器 +- 内存管理 +title: 容器选择指南:按操作、内存与失效规则挑对容器 +--- +# 容器选择指南:按操作、内存与失效规则挑对容器 + +## 这篇要解决什么:选错容器就是埋性能 bug + +vol3 把主力容器逐个拆了一遍——`array`、`vector`、`deque`/`list`/`forward_list`、`map`/`set`、`unordered_map`/`unordered_set`、还有 `span`。每一篇都在讲「这个容器内部长什么样、为什么这么设计」,这篇反过来:站在「我现在要存一坨数据,到底该选哪个」的角度,把它们摆到同一张桌上比。容器选错很少当场崩,它只会让你的程序慢、让引用莫名其妙失效、让 hot loop 里反复扩容——这些都是最难查的性能 bug,因为代码「能跑」,只是跑得窝火。 + +挑容器其实就看三件事:**你要对它做什么操作(复杂度)、数据在内存里怎么摆(局部性)、改完之后手里的迭代器还能不能信(失效规则)**。这三条想清楚,剩下都是细节。我们按这三条线走一遍,最后给一棵决策树收口。 + +## 先分清两大阵营:顺序容器与关联容器 + +标准库容器先分成两大类,这个分法决定了你问的第一个问题不一样。**顺序容器**(`array`、`vector`、`deque`、`list`、`forward_list`)按「位置」存数据,元素在容器里的次序就是你放进去的次序,你关心的是「我要在第几个位置插、在第几个位置删」。**关联容器**(`map`/`set` 和它们的 `unordered` 版)按「键」存数据,元素的次序由键决定(有序)或由哈希决定(无序),你关心的是「我按什么来查」。 + +关联容器内部又分两小类。`map`/`set`/`multimap`/`multiset` 是**有序**的,底层是红黑树,按 key 排好序,查找是稳定的 `O(log n)`,还能按范围遍历。`unordered_map`/`unordered_set` 这一组是**无序**的,底层是哈希表,查找平均 `O(1)` 但最坏 `O(n)`(全撞同一个桶时),不能按序遍历。一句话区分:**要不要按 key 排序遍历?要,就红黑树;不要,就哈希换平均 O(1)**。这个权衡我们在 [map 与 set 深入](06-map-set-deep-dive.md) 和 [unordered_map 与 set 深入](07-unordered-map-set-deep-dive.md) 两篇里都实测过。 + +## 复杂度速查:按操作挑容器 + +把复杂度摊成一张表,挑容器时直接对照你要做的操作。注意表里说的都是「操作本身」的代价,定位(找到要操作的位置)通常要另算。 + +| 容器 | 随机访问 | 头部插删 | 尾部插删 | 中间插删 | 按 key 查找 | +|------|---------|---------|---------|---------|------------| +| `array` | O(1) | — | — | — | — | +| `vector` | O(1) | O(n) | 摊还 O(1) | O(n) | — | +| `deque` | O(1) | O(1) | O(1) | O(n) | — | +| `list` | O(n) | O(1) | O(1) | O(1)(已有迭代器) | — | +| `forward_list` | O(n) | O(1) | — | O(1)(已有迭代器) | — | +| `map` / `set` | — | — | — | O(log n) | O(log n) | +| `unordered_map` / `set` | — | — | — | 平均 O(1) | 平均 O(1) 最坏 O(n) | + +这张表里有几个最容易误读的点,单独拎出来说。第一个是 `list` / `forward_list` 的「中间插入 O(1)」——这个 O(1) 只针对插入**动作本身**(链表改两个指针),前提是你**已经持有那个位置的迭代器**;如果你还得先从头遍历去找位置,定位那一步就是 O(n),加起来还是 O(n)。很多人看到「list 插入 O(1)」就以为 list 适合频繁增删,其实绝大多数「频繁增删」的场景,定位成本和 cache 不友好会把 list 拖得比 vector 还慢。第二个是 `vector` 尾部那个「摊还 O(1)」——单次扩容确实 O(n),但它分摊到 N 次 push_back 上每次还是常数,所以平均是 O(1);只要记得 `reserve`,扩容次数能压到几乎为零。第三个是 `deque`,它头尾插删都是 O(1) 看着很美,但中间插删是 O(n),而且代价还比 vector 更重(分段结构要搬动更多),所以 deque 是「两端频繁进出的队列」专属,别拿它当通用容器。 + +## 内存局部性:连续 vs 节点,性能的分水岭 + +复杂度表只能告诉你「渐进快慢」,但同样标着「O(1) 遍历」的两个容器,真实速度能差一个数量级——差距就在内存局部性上。存储方式决定了数据在内存里怎么摆,进而决定 CPU cache 命不命中。 + +顺序容器按存储方式分三档。`array`、`vector` 是**连续**内存,元素紧挨着放,遍历时一整个 cache line 一起进 L1,prefetcher 还能预取下一段。`deque` 是**分段连续**——内部是一组固定大小的块(chunk),块内连续、块间不连续,所以随机访问要算「第几块的第几个」,遍历在块内顺滑、跨块会断一下。`list` / `forward_list` 是**节点**存储,每个元素单独 new 一个节点,节点之间靠指针串起来,在内存里东一个西一个,遍历时几乎每次都要跳到一个新地址,cache 命中率极差。关联容器全是节点存储:红黑树一个节点、哈希表一个桶里挂一串节点,局部性都不如连续容器。 + +这个差距不是理论上的,跑一下就明白。 + +```cpp +#include +#include +#include +#include + +int main() +{ + constexpr int N = 1'000'000; + std::vector v(N); + std::list l; + for (int i = 0; i < N; ++i) { + v[i] = i; + l.push_back(i); + } + + volatile long long sink = 0; + + auto t0 = std::chrono::high_resolution_clock::now(); + long long sv = 0; + for (auto x : v) { sv += x; } + sink += sv; + auto t1 = std::chrono::high_resolution_clock::now(); + + long long sl = 0; + for (auto x : l) { sl += x; } + sink += sl; + auto t2 = std::chrono::high_resolution_clock::now(); + + auto us_v = std::chrono::duration_cast(t1 - t0).count(); + auto us_l = std::chrono::duration_cast(t2 - t1).count(); + std::printf("vector 遍历 %lld us, list 遍历 %lld us, list 慢 %.2fx\n", + us_v, us_l, us_v ? (double)us_l / us_v : 0.0); + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/cache_bench /tmp/cache_bench.cpp && /tmp/cache_bench +``` + +跑下来 `vector` 遍历会比 `list` 快好几倍(具体倍数跟机器和 cache 大小有关,量级是数倍而不是百分之几)——两者遍历都是 O(n)、每次加法都是 O(1),但 `vector` 的连续内存吃满 cache,`list` 的每个节点都要单独访存。这就是「为什么默认用 vector」的底层理由:在绝大多数「存一坨数据然后遍历」的场景里,连续内存带来的 cache 红利,远超过链表省下的那点搬移开销。**只有当你真的需要在已知位置频繁增删、且增删代价明显高于遍历代价时,list 才可能赢**——这个条件比直觉里苛刻得多。 + +## 迭代器失效速查:改了容器,手里的引用还能不能用 + +第三个维度是迭代器失效。你拿到一个迭代器或引用,然后对容器做了插入/删除,那个迭代器还能不能继续用?这直接决定你能不能「边遍历边删」「把引用存起来以后用」。下面这张表是 cppreference 上各容器「Iterator invalidation」小节的汇总,权威且值得背下来。 + +| 容器 | 插入(insert / push) | 删除(erase / pop) | +|------|----------------------|--------------------| +| `vector` / `string` | 发生重分配则全部失效;否则插入点之后的失效 | 擦除点及之后全部失效 | +| `deque` | **全部失效** | **全部失效** | +| `list` / `forward_list` | 不失效 | 仅被删元素失效 | +| `map` / `set` 等 | 不失效 | 仅被删元素失效 | +| `unordered_map` / `set` 等 | 触发 rehash 则失效;否则不失效 | 仅被删元素失效 | + +这张表里要特别盯住 `deque` 那一行。很多人把 deque 当成「头尾能 O(1) 的 vector」来用,但 vector 在不扩容时 erase 只失效点之后的,而 **deque 任何 erase 都会让全部迭代器失效**——这是 deque 分段结构搬移块指针导致的。如果你在代码里「存了 deque 的迭代器,之后又 erase 了一下」,几乎一定踩坑。相对地,节点容器(`list`、`map`、`set` 及其 unordered 版)的最大福利就是**插入永不失效、删除只失效被删的那个**,所以它们天然支持「边遍历边按迭代器删」「长期持有指向元素的引用」。 + +还有个 unordered 容器专属的细节:rehash。`unordered_map` 在装填因子超过 `max_load_factor`(默认 1.0)时会 rehash(扩桶),这一下会让所有迭代器失效(但引用和指针**不**失效,这是标准明确保证的)。对策是提前 `reserve(n)` 把桶数撑够,既避免 hot loop 里反复 rehash,也避免迭代器突然失效。 + +## 选择决策树 + +把三条线拧成一棵树,从最该先问的问题往下走。 + +第一刀切在「大小编译期知不知道」:知道且不变,直接 `array`——零堆分配、能 constexpr、放静态区省 RAM,没有比它更便宜的。不知道、要变长,进第二刀。第二刀切在「是不是按键查找」:是,进关联容器分支——要按 key 有序遍历就用 `map`/`set`(O(log n)),只要平均 O(1) 查找就用 `unordered_map`/`unordered_set`(记得 reserve);不是按键查找,进顺序容器分支。第三刀切在「频繁在哪插删」:频繁头尾进出,`deque`;只在尾部增长,`vector`(务必 reserve);频繁在已知中间位置增删且不需要随机访问,`list`;以上都不沾,默认 `vector`。 + +```text +大小编译期已知且不变? +├─ 是 → array +└─ 否 + ├─ 按键查找? + │ ├─ 要按 key 有序遍历 → map / set (O(log n)) + │ └─ 只要平均 O(1) 查找 → unordered_map/set (记得 reserve) + └─ 按位置存 + ├─ 频繁头尾进出 → deque + ├─ 主要尾部增长 → vector (+ reserve) + ├─ 已知位置频繁增删 → list (确认定位+cache 不是瓶颈) + └─ 其余 → vector (默认) +``` + +两个补充。一是只要「借用一阵子」、不想转移所有权,用 `span`——它是「array/vector/C 数组的统一只读视图」,零拷贝传参的标配,详见 [span 深入](08-span.md)。二是 C++23 起有了新选项:想要「有序 + cache 友好」的 map,看 `flat_map`(底层是排序 vector);想要「容量固定、绝不堆分配」的变长容器,看 C++26 的 `inplace_vector`——这俩我们放到 [新标准容器](10-new-containers-cpp23-26.md) 单独讲。 + +## 几个最常见的误选 + +把踩坑频率高的几个列一下,挑容器时先自检。第一种,**「增删多所以用 list」**——忽略了定位成本和 cache 不友好,绝大多数情况下 vector 加 erase 反而更快,list 只在你确实长期持有大量迭代器、且增删远多于遍历时才划算。第二种,**unordered 容器不 reserve**——往里塞 N 个元素却不 `reserve(N)`,中间会触发多次 rehash,每次 rehash 重哈希全部元素,hot path 上白白浪费。第三种,**vector 反复 push_back 不 reserve**——同理,扩容时整块搬移,reserve 一下能消掉绝大部分拷贝。第四种,**跨容器传引用不看失效规则**——尤其在 deque 上存了迭代器又改了容器,或者对着 vector 边遍历边 erase 不更新迭代器,这类 bug 编译器不会提醒,运行时才炸。 + +## 临了收几句 + +挑容器,先把三件事问清楚:操作复杂度、内存局部性、迭代器失效。这三条对得上,八九不离十;细节(异常安全、自定义分配、异构查找)再回到各容器的深入篇看。一个朴素但好用的默认值:**拿不准就 vector**,它连续、尾部摊还 O(1)、接口最全,是覆盖面最广的安全牌,等你量出它真的成了瓶颈再换。下一篇我们进入容器适配器——`stack`、`queue`、`priority_queue`,它们不是新容器,而是把底层容器「包」成栈/队列/堆的接口外壳。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [容器库总表(含迭代器失效说明)— cppreference](https://en.cppreference.com/w/cpp/container) +- [容器迭代器失效规则(按操作分)— cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) +- [std::vector 的 Iterator invalidation 小节 — cppreference](https://en.cppreference.com/w/cpp/container/vector#Iterator_invalidation) diff --git a/documents/vol3-standard-library/01-initializer-lists.md b/documents/vol3-standard-library/01-initializer-lists.md deleted file mode 100644 index fc8b25286..000000000 --- a/documents/vol3-standard-library/01-initializer-lists.md +++ /dev/null @@ -1,238 +0,0 @@ ---- -chapter: 3 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 详解成员初始化列表 -difficulty: intermediate -order: 1 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 5 -tags: -- cpp-modern -- host -- intermediate -title: 初始化列表 ---- -# 构造函数优化:初始化列表 vs 成员赋值 - -在嵌入式 C++ 项目中,我们很容易把精力放在"看得见"的地方:中断、DMA、时序、缓存命中率、Flash/RAM 占用……而对于构造函数这种"看起来只执行一次"的代码,往往下意识地放松了警惕。 - -但实际上,在 **对象创建频繁、内存紧张、构造路径复杂** 的系统中,构造函数的写法,直接影响: - -- 是否产生多余的构造 / 析构 -- 是否引入隐藏的默认初始化成本 -- 是否破坏对象的不变量 -- 是否在编译期就已经"输掉了优化空间" - -而这些问题,**几乎都集中体现在一个地方:你是否使用了初始化列表。** - ------- - -## 一、一个常见、但并不"无害"的写法 - -很多人最早接触 C++ 时,构造函数往往是这样写的: - -```cpp -class Timer -{ -public: - Timer(uint32_t period) - { - period_ = period; - enabled_ = false; - } - -private: - uint32_t period_; - bool enabled_; -}; - -``` - -乍一看没有任何问题,逻辑清晰、可读性也不错。 - -但在编译器眼中,这段代码的真实含义是: - -1. `period_` 被 **默认初始化** -2. `enabled_` 被 **默认初始化** -3. 进入构造函数体 -4. 对两个成员执行 **赋值操作** - -也就是说,**成员至少被"处理"了两次**。 - -在桌面平台上,这种开销通常可以忽略;但在嵌入式系统里,尤其是: - -- 构造对象数量多 -- 成员是结构体 / 数组 / STL 容器 -- 构造发生在启动阶段(Boot / Driver Init) - -这个"看不见的默认初始化"就开始变得真实存在了。 - ------- - -## 二、初始化列表并不是"语法糖" - -对比一下使用初始化列表的写法: - -```cpp -class Timer -{ -public: - Timer(uint32_t period) - : period_(period) - , enabled_(false) - {} - -private: - uint32_t period_; - bool enabled_; -}; - -``` - -这里的关键变化并不是"少写了几行代码",而是 **对象生命周期发生了变化**。这里我们的成员初始化变得更加直接——**直接在构造阶段完成初始化**,换句话说,**初始化列表不是赋值,它是构造的一部分**。 - -## 三、某些成员,根本"不能被赋值" - -在嵌入式系统中,这种情况并不少见。 - -#### 1. `const` 成员 - -```cpp -class Device -{ -public: - Device(uint32_t id) - : id_(id) - {} - -private: - const uint32_t id_; -}; - -``` - -`const` 成员 **只能在初始化阶段赋值一次**,构造函数体内的赋值在语义上是非法的。这不是语法限制,而是语言层面对"对象不变量"的保护。 - ------- - -#### 2. 引用成员 - -```cpp -class Driver -{ -public: - Driver(GPIO& gpio) - : gpio_(gpio) - {} - -private: - GPIO& gpio_; -}; - -``` - -引用一旦绑定,就不能再指向其他对象。因此,**初始化列表是唯一正确的写法**。 - ------- - -#### 3. 没有默认构造函数的成员 - -在你自己的框架代码中,这种类型其实非常常见: - -```cpp -class SpiBus -{ -public: - explicit SpiBus(uint32_t base_addr); -}; - -``` - -如果一个类作为成员存在: - -```cpp -class Sensor -{ -public: - Sensor() - : spi_(SPI1_BASE) - {} - -private: - SpiBus spi_; -}; - -``` - -此时如果不用初始化列表,代码甚至无法通过编译。 - ------- - -## 四、初始化列表带来的"语义完整性" - -在嵌入式工程里,我们经常强调 **"对象在构造完成后,必须处于可用状态"**。初始化列表天然符合这一原则。 - -```cpp -class RingBuffer -{ -public: - RingBuffer(uint8_t* buf, size_t size) - : buffer_(buf) - , size_(size) - , head_(0) - , tail_(0) - {} - -private: - uint8_t* buffer_; - size_t size_; - size_t head_; - size_t tail_; -}; - -``` - -这种写法传达的信息非常明确: - -> **对象一旦构造完成,内部状态就是完整、自洽的。** - -而如果把初始化拆散在构造函数体中,实际上就允许了"半初始化状态"的存在,这在底层系统中是非常危险的设计信号。 - ------- - -## 五、编译器优化视角:初始化列表 = 更大的优化空间 - -从编译器的角度看: - -- 初始化列表提供了 **确定的构造语义** -- 成员的初始值在构造阶段已知 -- 更容易进行: - - 常量传播 - - 构造消除 - - 栈上对象合并 - - 甚至在某些场景下完全消除对象 - -尤其是在你大量使用 `constexpr`、`inline`、模板时,**初始化列表是编译期优化的前提条件之一**。 - ------- - -## 在线运行 - -在线对比构造函数体内赋值与初始化列表的差异,观察 const 成员和引用成员的初始化方式: - - - -## 最后 - -初始化列表并不是什么"高级技巧",其实并不复杂,对于嵌入式系统中,**每一次多余的初始化,都会真实地变成指令、变成 Flash、变成时间**。而初始化列表,正是那种**不写就亏、写了稳赚**的现代 C++ 基本功。 diff --git a/documents/vol3-standard-library/02-array.md b/documents/vol3-standard-library/02-array.md new file mode 100644 index 000000000..a9b155de4 --- /dev/null +++ b/documents/vol3-standard-library/02-array.md @@ -0,0 +1,158 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 讲透 std::array:作为聚合类型零开销包住 C 数组、不退化为指针、std::get 与结构化绑定、永不失效的迭代器、constexpr + 编译期查表,以及与 C 数组和 vector 的精确边界 +difficulty: intermediate +order: 2 +platform: host +reading_time_minutes: 7 +related: +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- intermediate +- array +- 容器 +title: array:编译期固定大小的聚合容器 +--- +# array:编译期固定大小的聚合容器 + +## array 到底是什么:零开销包住 C 数组的聚合类 + +`std::array` 是 C++11 给 C 数组补的「现代化外壳」。C 数组 `T[N]` 有几个老毛病:传参时退化为指针(丢掉长度)、没有 `.size()`、不能整体拷贝赋值、没法当函数返回值。`std::array` 把这块连续内存包成一个类模板,补上 STL 接口,而且——这是关键——**它是个聚合类型(aggregate),没有任何额外开销**:`sizeof` 和 C 数组一模一样,没有虚函数、没有虚指针、没有额外成员。 + +```cpp +std::array a = {1, 2, 3, 4, 5}; // 大小 5 在编译期定死 +a.size(); // 5 +a[0]; // 1,O(1) +a.data(); // int*,指向底层连续内存 +``` + +那个 `N` 是模板参数,是编译期常量。这意味着 array 的大小是类型的一部分——`std::array` 和 `std::array` 是两个不同的类型,不能互相赋值。代价换来的是零动态分配:array 占的内存就是那块连续数据,放在栈上或静态区,不碰堆。 + +## 和 C 数组的精确对比:不退化、有接口、能当对象 + +array 相对 C 数组的改进,一条条数清楚。第一,**不退化为指针**:C 数组传给函数会退化成 `T*`,丢掉长度;array 是个对象,传参时完整保留类型(包括 N),你要么传 `const std::array&`,要么显式 `.data()` 给 C 接口。第二,**有 STL 接口**:`.size()`、`.empty()`、`.begin()` / `.end()`、`.data()`、`operator[]`、`.at()`,能直接喂给 `` 和范围 for。第三,**能整体拷贝赋值**:`auto b = a;` 就是逐元素拷贝,还能当函数返回值、当类的成员——这些 C 数组都做不到。 + +```cpp +std::array make() { return {1, 2, 3, 4}; } // C 数组做不到 +auto a = make(); +auto b = a; // 整体拷贝,C 数组做不到 +b.fill(0); // 一把清零 +``` + +但底层还是那块连续内存。标准保证 array 是聚合,所以 `sizeof(std::array)` 就等于 `sizeof(T) * N`(没有额外成员、没有尾部 padding 之外的浪费)。它没有额外开销,只是多了接口和类型安全。 + +## 和 vector 的边界:何时该用定长 + +array 和 vector 的分界线就一条:**大小编译期知道吗**。如果大小在编译期能定死、且不会变,用 array——零堆分配、零开销、可以 `constexpr`、放静态区省 RAM。如果大小运行时才确定、或需要增删,用 vector。 + +代价是对等的:array 大小是类型的一部分(`array` 和 `array` 不能通用),函数想接受「任意大小的 int 数组」就没法用 array(得用 `span` 或模板);vector 没这个限制,但有堆分配和扩容开销。一句话:**定长用 array,变长用 vector**,中间地带(运行期已知大小但不想堆分配)可以等 C++26 的 `inplace_vector`,或自己管 buffer 配 `span`。 + +## 作为聚合类型的特权:std::get、结构化绑定、tuple 接口 + +array 是聚合类型,这让它在 C 数组之外还吃到一份「类 tuple」的红利。`std::get(arr)` 能按编译期下标取元素(返回引用,带类型安全);C++17 的结构化绑定能直接把小 array 拆成变量;`std::tuple_size`、`std::tuple_element` 也认识 array,所以 array 能塞进那些吃 tuple-like 类型的泛型代码里。 + +```cpp +std::array a = {10, 20, 30}; +std::get<1>(a); // 20,编译期下标,类型安全 +auto [x, y, z] = a; // 结构化绑定:x=10, y=20, z=30 +static_assert(std::tuple_size_v == 3); +``` + +这些在 C 数组上都没有——C 数组拿不到 `std::get`,也不支持结构化绑定。对那种「固定几个值」的小数组(比如三维坐标、RGB),array 加结构化绑定比写个 struct 还顺手。 + +## 复杂度、迭代器失效与异常安全 + +复杂度一目了然:随机访问 `operator[]` 和 `.at()` 都是 O(1),遍历 O(n),没有扩容、没有重分配——因为大小定死。 + +**迭代器失效**这块,array 是最省心的:它永不失效。因为 array 是固定大小的聚合,没有扩容、没有插入删除(接口里压根没有 `push_back` / `insert`),迭代器、引用、指针一旦拿到,只要 array 对象本身还活着,就一直有效。这点比 vector(扩容全失效)、deque、list 都干净。 + +异常安全上有个要留意的点:`.at(i)` 会做边界检查,越界抛 `std::out_of_range`;而 `operator[]` 不检查,越界是未定义行为。在异常关闭的环境(比如 `-fno-exceptions`),`.at()` 的越界会退化成 `std::terminate`,所以那种场景只能用 `operator[]` 并自己保证索引正确。 + +## 跑跑看:零开销与 constexpr + +光说「零开销」不够实在,咱们跑跑看。先确认 sizeof 真的和 C 数组一样: + +```cpp +#include +#include + +int main() +{ + int raw[8]; + std::array arr; + std::cout << "sizeof(int[8]) = " << sizeof(raw) << '\n'; + std::cout << "sizeof(array) = " << sizeof(arr) << '\n'; + std::cout << "data() 指向首元素? " << (arr.data() == &arr[0]) << '\n'; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/array_sizeof /tmp/array_sizeof.cpp && /tmp/array_sizeof +``` + +```text +sizeof(int[8]) = 32 +sizeof(array) = 32 +data() 指向首元素? 1 +``` + +sizeof 完全相等,没有额外开销——array 就是那块连续内存,套了个类。`data()` 也确实指向首元素,可以放心交给 C 接口或 DMA。 + +array 的另一大本事是 **constexpr**——它能在编译期完成初始化和计算,生成的数据直接放进只读段。一个经典用法是编译期生成 CRC 查表: + +```cpp +#include +#include + +constexpr std::array make_crc_table() +{ + std::array t{}; + for (std::size_t i = 0; i < 256; ++i) { + uint32_t crc = static_cast(i); + for (int j = 0; j < 8; ++j) { + crc = (crc & 1) ? (0xEDB88320u ^ (crc >> 1)) : (crc >> 1); + } + t[i] = crc; + } + return t; +} + +// 编译期算完,进只读段;运行时零开销 +constexpr auto crc_table = make_crc_table(); +static_assert(crc_table.size() == 256); +static_assert(crc_table[0] == 0x00000000u); // 输入 0,结果 0 +``` + +这 256 项表在编译期就算好了,程序运行时直接从只读段读,既不占 RAM、也不花运行时 CPU。这种「编译期查表」是 array + constexpr 的黄金组合——C 数组配 constexpr 做不到这么干净(尤其涉及拷贝返回时)。 + +## 延伸:嵌入式里的 array(DMA / flash / 栈) + +array 因为零堆分配、连续内存、可 constexpr,在嵌入式里特别受欢迎,这里补几个实战要点(主线之外,按需取用)。第一,**连续内存保证**:`.data()` 返回的指针指向一段连续存储,可以安全交给 DMA 或 HAL,前提是元素类型是 trivially copyable。第二,**放静态区省 RAM**:大数组用 `static` 或放进 `.bss`,查表数据用 `constexpr` 直接进 flash,不占 RAM。第三,**栈深度**:小数组放栈上没问题,但要留意任务 / ISR 的栈深度限制,别在窄栈里放大 array。 + +## 临了收几句 + +array 是 C 数组的现代化外壳,零开销、有 STL 接口、不退化、能当对象,还能借聚合身份吃到 `std::get` 和结构化绑定。它永不失效迭代器、可以 constexpr、零堆分配——只要大小编译期能定死,它就是比 C 数组和 vector 都更合适的选择。下一篇我们看它的「动态版」vector,从固定走向可变,代价是堆和扩容。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::array — cppreference](https://en.cppreference.com/w/cpp/container/array) +- [聚合类型 — cppreference](https://en.cppreference.com/w/cpp/language/aggregate_initialization) +- [容器迭代器失效规则总表 — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/vol3-standard-library/02-span.md b/documents/vol3-standard-library/02-span.md deleted file mode 100644 index cc35b3e68..000000000 --- a/documents/vol3-standard-library/02-span.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -chapter: 7 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: C++20数组视图 -difficulty: intermediate -order: 2 -platform: host -prerequisites: -- 'Chapter 6: RAII与智能指针' -reading_time_minutes: 8 -tags: -- cpp-modern -- host -- intermediate -title: std::span 数组视图 ---- -# 嵌入式C++教程:std::span——轻量、非拥有的数组视图 - -把 `std::span` 想象成 C++ 里的「透明的传送带」:它不拥有上面的货物(内存),只是平静又高效地告诉你"这里有多少个元素、从哪里开始"。在嵌入式里,我们经常需要把一段内存传给函数——既不想拷贝,也不想丢失类型信息或边界信息,`std::span` 就是为这种场景生的。 - -或者说,直到C++20,一个标准的视图容器才出现。 - -- `std::span` 是**非拥有**(non-owning)的视图:不负责内存释放。 -- 它通常是一个指针 + 长度(非常轻量,拷贝成本低)。 -- 函数参数用 `std::span` 可以优雅地接受 `T[]`、`std::array`、`std::vector`、裸指针+长度 等多种来源。 -- **关键注意**:不要让 `span` 的生存期超过底层数据的生存期 —— 悬垂指针依旧会把你咬一口。 - ------- - -## 引子:为什么不直接用指针或 vector? - -在嵌入式代码里,我们常看到这样的函数签名: - -```cpp -void process_buffer(uint8_t* buf, size_t n); - -``` - -这招确实灵活,但缺点:读者得同时记住 `buf` 的类型、长度单位是"元素数"还是"字节数"、函数是否要修改数据……出错的地方太多。 `std::span` 把这些语义显式化:类型和值(length)都在同一个对象里,阅读性和安全性都提升了。 - ------- - -## 基本用法 - -```cpp -#include -#include -#include -#include - -void print_bytes(std::span s) { - for (auto b : s) std::cout << std::hex << int(b) << ' '; - std::cout << std::dec << '\n'; -} - -int main() { - uint8_t buffer[] = {0x10, 0x20, 0x30}; - std::vector v = {1,2,3,4}; - std::array a = {9,8,7}; - - print_bytes(buffer); // 从内置数组构造 - print_bytes(v); // 从 vector 构造 - print_bytes(a); // 从 std::array 构造 - print_bytes({v.data(), 2}); // 从 pointer + size 构造 -} - -``` - -`print_bytes` 用 `std::span` 接收输入:既说明了不修改内容,又接受多种容器来源,调用方无需拷贝数据。 - ------- - -## 动态与静态 extent - -`std::span` 有两种形态: - -- `std::span`(或 `std::span`):运行时大小; -- `std::span`:编译期固定元素数 `N`(称为静态 extent)。 - -示例: - -```cpp -int arr[4]; -std::span s_fixed(arr); // 只有长度为 4 的数组能绑定 -std::span s_dyn(arr, 4); // 任意长度,运行时记录 - -``` - -静态 `Extent` 可以在某些场景下启用额外的编译期检查或优化,但在嵌入式中,动态 extent 更常用(因为 buffer 长度常由运行时决定)。 - ------- - -## 有用的成员函数 - -```cpp -s.size(); // 元素个数 -s.size_bytes(); // 字节数(注意!元素个数 * sizeof(T)) -s.data(); // 指向首元素的指针(可能为 nullptr 当 size()==0) -s.empty(); -s.front(), s.back(); -s[i]; // 下标,不做运行时检查(与 operator[] 语义一致) -s.subspan(offset, count); // 切片,返回新的 span(仍为 non-owning) -s.first(n), s.last(n); // 前 n 个或后 n 个元素视图 -std::as_bytes(s); // 将 span 视为 span -std::as_writable_bytes(s); // 视为 span(当 T 可写时) - -``` - -注意:`operator[]` 不检查越界;如果需要边界检查,自行用 `at`-like wrapper 或在调试时加断言。 - ------- - -## 进阶示例:subspan 与字节操作 - -```cpp -#include -#include // for std::byte - -void recv_packet(std::span buffer) { - if (buffer.size() < 4) return; - auto header = buffer.first(4); - uint16_t len = header[2] | (header[3] << 8); - - if (buffer.size() < 4 + len) return; - auto payload = buffer.subspan(4, len); - - // 把 payload 当作字节流传给 CRC 函数 - auto bytes = std::as_bytes(payload); - // crc_check(bytes.data(), bytes.size()); // 示例:调用检验函数 -} - -``` - -这种把整体 buffer 切片成 header/payload 的写法尤其适合嵌入式协议解析,简洁而安全(只要你保证传进来的 `buffer` 有效)。 - ------- - -## 当做函数参数的最佳实践 - -把 API 设计成接收 `std::span` 有几个好处: - -- 调用者可以传入数组、`std::array`、`std::vector` 或裸指针+长度; -- 函数签名清楚地表达"这是一个视图(可能只读)"; -- 函数内不需要 template 泛型来支持各种容器。 - -示例: - -```cpp -void process(std::span data); // 明确:不修改数据 -void mutate(std::span data); // 明确:会修改数据 - -``` - -这比写 `template void process(const Container& c)` 更直观,也避免了不必要的编译膨胀。 - ------- - -## 常见坑 - -1. **悬垂视图**:最常见错误。不要把 `std::span` 绑定到局部 `std::vector` 的 `data()` 并把它返回给调用者: - - ```cpp - std::span bad() { - std::vector v = {1,2,3}; - return v; // ❌ v 被销毁,返回的 span 悬垂 - } - - ``` - -1. **以为有所有权**:span 不持有内存,不会析构或释放。若需要所有权,用 `std::vector`、`unique_ptr` 等。 - -1. **不恰当的字节视图**:`std::as_bytes` 返回 `span`,用于只读字节访问;`as_writable_bytes` 仅在底层可写时使用。 - -1. **越界访问**:`operator[]` 不检查边界。必要时做显式检查或使用调试断言。 - -1. **不是以 null 结尾的字符串**:`std::span` 不是 `C` 字符串,不保证以 `'\0'` 结尾。处理字符串请用 `std::string_view` 或明确长度处理。 - ------- - -## 与 `std::string_view` 的对比 - -- `std::string_view` 是专门为字符序列设计的(只读视图),并带有字符串语义(常用于文本)。 -- `std::span`/`std::span` 通用于任意元素类型,包括可写情况。 - 在处理二进制协议/缓冲区时,`std::span` 更合适;处理不可变文本时,用 `string_view` 更语义化。 - ------- - -## 嵌入式场景快速举例 - -- DMA 回调把数据放进固定 buffer,回调把 `std::span` 传给处理函数,无需拷贝。 -- 从 Flash 读出数据到缓冲区,然后用 `std::span` 切片解析头和块。 -- 在中断或实时路径中传递小段数据,`span` 的拷贝开销极低。 - ------- - -## 代码小贴士 - -1. 将函数参数写成 `std::span`,以表达只读意图。 -2. 若想允许传入大小为 N 的 buffer,但不更改逻辑,可接受 `std::span`(静态 extent)。 -3. 使用 `subspan`, `first`, `last` 构造子视图,而非手动计算指针偏移。 -4. 在公共 API 文档里明确说明:**span 不负责生命周期管理**。 - ------- - -## 在线运行 - -在线体验 std::span 从不同容器类型构造视图、subspan 切片操作: - - - -## 速查 API - -`s` 为 `std::span`: - -- `s.size()`, `s.size_bytes()`, `s.data()`, `s.empty()` -- `s[i]`(无边界检查)、`s.front()`、`s.back()` -- `s.begin()`, `s.end()`(支持范围 for) -- `s.subspan(offset, count)`, `s.first(n)`, `s.last(n)` -- `std::as_bytes(s)`、`std::as_writable_bytes(s)` diff --git a/documents/vol3-standard-library/01-vector-deep-dive.md b/documents/vol3-standard-library/03-vector-deep-dive.md similarity index 97% rename from documents/vol3-standard-library/01-vector-deep-dive.md rename to documents/vol3-standard-library/03-vector-deep-dive.md index 5c571f3f0..cc027be2d 100644 --- a/documents/vol3-standard-library/01-vector-deep-dive.md +++ b/documents/vol3-standard-library/03-vector-deep-dive.md @@ -5,21 +5,21 @@ cpp_standard: - 14 - 17 - 20 -description: "从三指针内部表示出发,讲透 std::vector 的扩容代价、迭代器失效全景、move_if_noexcept 异常安全,以及 C++20 constexpr vector 与 erase/erase_if" +description: 从三指针内部表示出发,讲透 std::vector 的扩容代价、迭代器失效全景、move_if_noexcept 异常安全,以及 C++20 + constexpr vector 与 erase/erase_if difficulty: intermediate -order: 1 +order: 3 platform: host prerequisites: -- '卷一:vector 基础用法(size / capacity / push_back)' -reading_time_minutes: 16 +- 卷一:vector 基础用法(size / capacity / push_back) +reading_time_minutes: 14 tags: - host - cpp-modern - intermediate - vector -title: "vector 深入:三指针、扩容与迭代器失效" +title: vector 深入:三指针、扩容与迭代器失效 --- - # vector 深入:三指针、扩容与迭代器失效 这一篇,笔者想跟各位好好聊聊 `std::vector` 的实现层。 @@ -268,7 +268,7 @@ int main() control; // 中控数组,每项指向一个块 + // 每个 Block 是一段连续内存,装若干元素 +}; +// 随机访问:block = control[i / chunk_size],元素 = block[i % chunk_size] +``` + +这个结构带来三个特点。第一,**头尾 push/pop 都是 O(1)**:尾部满了就加新块,头部满了就在前面加块(或块内从后往前填),都不挪动已有元素——这就是它相对 vector 最大的优势。第二,**随机访问还是 O(1)**,`d[i]` 算出元素在第几个块,再取块内偏移;只是比 vector 多一次「中控 → 块」的指针解引,所以稍微慢一点。第三,**扩容不搬全部元素**:deque 满了只需扩中控数组(一组指针,很小),再挂新块,已有元素地址不变——比 vector 扩容(搬全部、迭代器全失效)温和得多。 + +代价是:内存不是一整块(对需要把数据传给 C 接口、或要连续 buffer 的场景不友好),而且「中控 + 多块」的结构本身有一定空间开销。 + +## list:双向链表,O(1) 中间插删 + splice + +`list` 是双向链表,每个节点存 `{前驱指针, 数据, 后继指针}`。它的核心卖点是:**已知位置(拿到迭代器)的插入删除是 O(1)**——只改几个指针,不挪动任何其他元素。而且**迭代器永不失效**(插入/删除只影响被删节点本身的迭代器),这点连 deque 和 vector 都做不到。 + +list 还有个独门绝技 **splice**:`l1.splice(pos, l2)` 能把 l2 的节点链直接「剪接」到 l1,整个过程 O(1),不拷贝任何元素——这是链表特有的能力,连续容器给不了。适合「把一个链表的某段零成本搬到另一个」的场景。 + +但 list 的短板也很要命。第一,**不支持随机访问**,没有 `operator[]`,要找第 1000 个元素得从头走 1000 步(O(n))。第二,**cache 极不友好**:节点分散在堆上各处,遍历时 CPU 预取失效、cache miss 频繁。后面我们会跑给你看,list 遍历比 vector 慢好几倍,就是这个原因。所以「中间插入 O(1)」这个优势,经常被「先要 O(n) 找到位置」加上「遍历慢」抵消——除非你真的拿着迭代器频繁插删,否则不一定划算。 + +## forward_list:省到极致的单向链表 + +`forward_list` 是单向链表,每个节点只存 `{后继指针, 数据}`,比 list 少一个前驱指针。它是 C++11 才加进来的,目标很明确:对标 C 手写单链表的「零开销」——当你只需要向前遍历、且内存敏感(比如嵌入式)时,没必要为用不到的反向能力多付一个指针的代价。 + +代价自然是不能反向遍历,而且**没有 O(1) 的 `push_back`**(得先 O(n) 走到尾),只有 `push_front` 是 O(1)。接口也比 list 精简:它**故意不提供 `size()`**——因为标准要求 `size()` 必须 O(1),而单链表做不到 O(1) 维护,干脆不给,要用得自己数。 + +## 跑跑看:遍历 vs 头插,两副完全相反的面孔 + +光说 list 遍历慢、vector 头插慢太抽象,咱们直接跑。先看遍历:`vector`、`deque`、`list` 各装一百万个 int,遍历求和。 + +```cpp +#include +#include +#include +#include +#include + +int main() +{ + const int N = 1000000; + std::vector v(N); + std::deque d(N); + std::list l; + for (int i = 0; i < N; ++i) { + v[i] = i; + d[i] = i; + l.push_back(i); + } + + volatile long long sink = 0; + auto bench = [&](auto& c, const char* name) { + auto t0 = std::chrono::high_resolution_clock::now(); + long long s = 0; + for (auto x : c) { + s += x; + } + sink = s; + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << name << ": " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + }; + + bench(v, "vector "); + bench(d, "deque "); + bench(l, "list "); + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/traversal /tmp/traversal.cpp && /tmp/traversal +``` + +```text +vector : 0.3 ms +deque : 0.44 ms +list : 1.9 ms +``` + +(GCC 16.1.1,本机;量级关系稳定。)list 比 vector 慢了六倍,比 deque 慢四倍——这就是节点分散、cache 不友好的真实代价。deque 因为分段连续,块内还是有局部性,所以比 list 快不少,但比一整块连续的 vector 还是慢一点。 + +再看一个反过来的场景:往头部插十万个元素。 + +```cpp +#include +#include +#include +#include +#include + +int main() +{ + const int N = 100000; + volatile int sink = 0; + + { + std::vector v; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + v.insert(v.begin(), i); // 每次 O(n) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "vector front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = v.size(); + } + { + std::deque d; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + d.push_front(i); // O(1) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "deque front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = d.size(); + } + { + std::list l; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) { + l.push_front(i); // O(1) + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::cout << "list front insert: " + << std::chrono::duration(t1 - t0).count() << " ms\n"; + sink = l.size(); + } + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/front_insert /tmp/front_insert.cpp && /tmp/front_insert +``` + +```text +vector front insert: 246 ms +deque front insert: 0.2 ms +list front insert: 4.8 ms +``` + +这下完全反过来:vector 头插要花 246ms,deque 只要 0.2ms——差了一千多倍。因为 vector 每次 `insert(begin)` 都要把所有元素往后挪一位,十万次下来是 O(n²);deque 和 list 的头插都是 O(1)。注意 deque 比 list 还快(list 每次要 malloc 一个节点,deque 只在块内填、偶尔加块),这也是 deque 在「双端增删」场景胜过 list 的原因。 + +这两组数据放一起看就清楚了:**没有银弹**。遍历密集就用 vector/deque,头插/中间插频繁就上 deque/list,选错了就是数量级的性能差距。 + +## 临了收几句:怎么选 + +| 需求 | 选 | +|------|----| +| 随机访问 + 尾部增删为主 | `vector` | +| 两头都要增删(队列 / 双端) | `deque` | +| 频繁在已知位置插删 / 需要 splice / 迭代器不能失效 | `list` | +| 极致省内存 + 只向前遍历(嵌入式) | `forward_list` | + +一句口诀:能用 vector 就用 vector,真要双端就 deque,真要链表特性才上 list / forward_list。顺序容器里,vector 几乎永远是默认答案,另外三个是「有明确需求时才换上去」的专项工具。关联容器我们前面讲完了 map 和 unordered_map,下一篇我们离开容器,去看标准库的迭代器与算法体系。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::deque — cppreference](https://en.cppreference.com/w/cpp/container/deque) +- [std::list — cppreference](https://en.cppreference.com/w/cpp/container/list) +- [std::forward_list — cppreference](https://en.cppreference.com/w/cpp/container/forward_list) +- [容器迭代器失效规则总表 — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/vol3-standard-library/05-object-size-and-trivial-types.md b/documents/vol3-standard-library/05-object-size-and-trivial-types.md deleted file mode 100644 index fda231363..000000000 --- a/documents/vol3-standard-library/05-object-size-and-trivial-types.md +++ /dev/null @@ -1,272 +0,0 @@ ---- -chapter: 3 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 探讨C++对象内存布局 -difficulty: intermediate -order: 5 -platform: host -prerequisites: -- 'Chapter 2: 零开支抽象' -reading_time_minutes: 12 -tags: -- cpp-modern -- host -- intermediate -title: 对象大小与平凡类型 ---- -# 嵌入式现代C++教程——对象大小、内存对齐、类型"平凡/标准布局"与聚合初始化 - -写底层代码、做嵌入式系统或者和 C 接口打交道时,常会被一串看似晦涩的名词绕晕:`sizeof`、`alignof`、`alignas`、`trivial`、`standard-layout`、`trivially_copyable`、聚合(aggregate)……这些概念看起来零碎,其实是一张互相勾连的地图:它们决定了对象的内存表现(object representation)、拷贝语义、以及能否安全地用 `memcpy`、能否与 C 结构体 ABI 兼容、以及初始化的灵活性。 - ------- - -## 先从「大小」和「对齐」说起:为什么 `sizeof` 不总是成员之和 - -`sizeof(T)` 报的是对象在内存中的**占据字节数**(即完整对象表示,需要包含必要的填充),而 `alignof(T)` 报的是该类型的**对齐约束**——也就是对象起始地址必须是 `alignof(T)` 的整数倍。 - -想象一栋楼(对象),不同房间(成员)有不同尺寸和对齐规则。为了让某些大件能正确放进房间,楼层之间可能需要空隙(填充,padding)。这些空隙在编译器看来是必须的。 - -看一个最常见的例子: - -```cpp -struct A { - char c; // 1 byte - int i; // 4 bytes, alignment 4 -}; -// typical layout on common ABIs: -// offset 0: c -// offset 1..3: padding -// offset 4..7: i -// sizeof(A) == 8 - -``` - -如果把顺序换一下: - -```cpp -struct B { - char a; // offset 0 - int i; // offset 4 (padding 3 bytes) - char b; // offset 8 - // padding 3 bytes to make sizeof multiple of alignof(B) (which is 4) - // sizeof(B) == 12 -} - -``` - -把两个 `char` 放在一起通常能减少填充: - -```cpp -struct C { - char a; - char b; - int i; - // char a@0, char b@1, padding@2-3, int@4..7 -> sizeof == 8 -} - -``` - -所以排序成员、把宽对齐的成员(比如 `double`、`int64_t`、SIMD 向量等)放在一起或放到结构尾部,是常见的内存压缩策略。对嵌入式系统,这常常能从不必要的 RAM 使用中挤出可观空间。 - -另外,结构的整体对齐是其成员中**最大对齐**的值。编译器还会在结构尾部加上尾部填充(tail padding),保证 `sizeof(T)` 是 `alignof(T)` 的倍数。这一点关系到数组元素的间隔、以及结构放进数组时的间隔。 - -可以通过 `alignas` 强制或改变对齐,例如为一个需要 16 字节对齐的 SIMD 缓冲区指定对齐: - -```cpp -struct alignas(16) Vec4 { - float x,y,z,w; // sizeof == 16, alignof == 16 -}; - -``` - -用 `alignas` 要小心:提高对齐会改变结构的 ABI 和 `sizeof`,并可能导致 unaligned access 的问题在某些平台被暴露(如果你在不支持的硬件上将对象放到不对齐地址,会崩溃)。 - ------- - -## trivial / trivially_copyable / standard-layout:为什么这些"类型属性"重要 - -C++ 标准将一组类型特性分拆开来,用以精确表达"这个类型的对象在内存中的行为"。这是从 C++11 开始的设计(把历史上的 POD 拆成几件事),对嵌入式和系统编程尤其重要,因为它决定了可否用 `memcpy`、可否与 C 互操作、以及优化空间。 - -先把几个经常被混淆的词放到一张图里(用自然语言): - -- **trivial(平凡)类型**:大体上就是具有"平凡"的特殊成员(默认构造、复制/移动构造、赋值、析构等都是编译器生成且没有自定义逻辑)的类型。换句话说,构造/复制/析构不会做任何运行时代码——对象的比特位就是对象表示,没有隐藏动作。 -- **trivially_copyable(可平凡拷贝)类型**:这类类型可以安全地通过按字节拷贝(`memcpy`)来复制(复制后目标对象具有同样的对象表示并且能正常析构等)。`trivially_copyable` 是能否使用 `memcpy` 的关键判据。 -- **standard-layout(标准布局)类型**:这样的类型有可预测的内存布局规则(比如非静态数据成员按声明顺序排布、对于与 C 互操作时有一定的保证)。它避免了复杂的访问控制、虚继承或多重基类导致的不可预测内存布局。 - -一个非常重要的事实是:以前老概念 `POD`(Plain Old Data)在 C++11 被拆分成 `trivial` 与 `standard-layout`;而 `POD` 在语义上就是"既 trivial 又 standard-layout"。很多与 ABI、C 互操作相关的安全假设都可以用 `std::is_standard_layout_v`、`std::is_trivially_copyable_v` 来检查。 - -为什么这些信息有用?因为它们直接影响到: - -- 是否可以把一个对象读写为字节序列(比如存到闪存、或者通过 DMA 直接从内存传输)。 - - **只有 `trivially_copyable` 的类型才可以安全使用 `memcpy` 来复制对象表示**。 -- 是否可以把 C++ 的类型当成 C 的 `struct` 去传给外部 C 接口(例如设备寄存器映射、bootloader 的数据结构)。 - - **通常要求 `standard-layout` 来保证布局兼容**。 -- 在常量表达式和零初始化上下文里如何表现(例如静态存储对象初始化和内存映像)。 - -举个示例来把这些概念结合一下: - -```cpp -struct S { - int x; - double y; - // 没有用户定义构造/析构/拷贝、没有虚函数、没有基类…… -}; -// S 通常是 trivial、trivially_copyable、standard-layout -> POD -static_assert(std::is_trivially_copyable_v); -static_assert(std::is_standard_layout_v); - -``` - -对比一个非平凡的类型: - -```cpp -struct T { - T() { /* do something */ } // user-provided ctor - int x; -}; -// T 不是 trivial(因为用户定义了构造函数);可能也不是 trivially_copyable。 - -``` - -再强调一条易错的点:**trivial ≠ trivially_copyable**,前者强调特殊成员(尤其默认构造)的"平凡性",后者强调按字节复制是否安全。实践中,判断是否能 `memcpy`,请用 `std::is_trivially_copyable_v`。 - ------- - -## 关于聚合(aggregate)与聚合初始化:从花括号到 C++20 的指定初始化 - -聚合是一个非常方便的类型类别:它允许用花括号直接列出成员来初始化对象(aggregate initialization),这在编写数据描述(比如设备描述表、配置结构)时极其直观,也天然适合 `constexpr` 和静态初始化。 - -经典的聚合(直观描述)是"没有用户自定义构造函数、没有虚函数、其非静态数据成员都是 public,并且没有基类(或者满足标准布局的限制)"——总之,编译器可以简单地把初始化聚合当成按成员顺序把值拷进对象表示中。 - -例子: - -```cpp -struct Point { int x, y; }; -Point p1 { 1, 2 }; // aggregate initialization, 成员按声明顺序赋值 - -``` - -聚合初始化的一个好处是,它允许部分初始化(剩下的成员会被默认初始化/零初始化,取决于上下文),并且常用于 `constexpr`: - -```cpp -struct Config { - int baud; - int parity; - int stop_bits; -}; - -constexpr Config default_cfg { 115200, 0, 1 }; - -``` - -### C++20 的指定(designated)初始化:更可读也更稳妥 - -C 早有的"指定初始化"(`.{member} = value`)到 C++20 被引入为正式语言特性。这使得聚合初始化更可读、对成员顺序不敏感,并且更便于维护(新增成员时旧代码不会因为顺序而出问题)。 - -用法示例: - -```cpp -struct S { - int a; - int b; - int c; -}; - -S s1 { .b = 2, .a = 1, .c = 3 }; // 成功:成员顺序不重要 -S s2 { .a = 1 }; // 只初始化 a,b 和 c 会做默认初始化(对内置类型通常为未定义或零,取决上下文) - -``` - -指定初始化也支持嵌套结构体和数组下标指定(类似 C 的 `[index] = value`)——这对初始化复杂硬件描述数据结构、寄存器布局、或者长表格非常实用。举个更贴近硬件的例子: - -```cpp -struct Header { - uint16_t id; - uint16_t flags; -}; - -struct Packet { - Header hdr; - uint8_t payload[8]; -}; - -Packet pkt { - .hdr = { .id = 0x1234, .flags = 0x1 }, - .payload = { [0] = 0xAA, [3] = 0x55 } // 只给第 0 和第 3 个元素赋值 -}; - -``` - -这带来几个实用的好处: - -- 可读性大幅提升:看到 `.flags = 0x1` 就明白含义,而不是靠位置猜测。 -- 抗扩展性:新增成员不会打破老代码(除非老代码依赖位置)。 -- 与 C 的兼容性更好(便于把 C 风格初始化范例搬到 C++ 中)。 - -注意事项:`designated init` 只适用于**聚合类型**,对于有用户自定义构造函数的类,不能用这种语法。 - ------- - -## 把它们连成一条实用的道路:嵌入式/底层工程师如何运用这些知识 - -现在把上面讲的点串成一些实战可用的原则,写成一段连续的叙述,帮你在做嵌入式 C++ 时少踩坑、代码更健壮。 - -当你要定义和 C 交互的数据结构(比如设备寄存器布局、bootloader metadata、序列化格式、DMA 缓冲区),通常要确保类型是**standard-layout**(以保证可预期的内存布局),并且最好是 **trivially_copyable**(以便快捷地 `memcpy` 或将一块内存解释为该结构)。在定义时,避免虚函数、避免私有非静态数据成员、不要写自定义构造/析构/拷贝操作。对重要的断言使用 `static_assert`: - -```cpp -static_assert(std::is_standard_layout_v, "MyRegs must be standard-layout for C-ABI compatibility"); -static_assert(std::is_trivially_copyable_v, "MyRegs must be trivially_copyable for memcpy usage"); - -``` - -内存对齐会影响 `sizeof` 和数组布局,若你的硬件或 DMA 要求特殊对齐(例如 16 字节对齐的缓存行或 SIMD),请使用 `alignas` 明确指定,并注意这会改变 `sizeof` 与 ABI。例如,一个被 `alignas(16)` 修饰的结构在数组中每个元素会占 16 的倍数字节。 - -写初始化代码时,优先使用花括号初始化与 C++20 的指定初始化。这不仅让代码可读,也降低了因为成员顺序变动而引入的 bug。用在寄存器或配置表上特别安全、直观。例如: - -```cpp -struct DeviceConfig { - uint32_t mode; - uint32_t timeout_ms; - uint8_t flags; -}; - -DeviceConfig cfg { - .mode = 3, - .timeout_ms = 1000, - // .flags 未指定 -> 按规则零/默认初始化 -}; - -``` - -当你需要节约 RAM,记住重新排列字段可以显著减少结构尺寸,尤其是在大量对象或数组情形下。把宽对齐的成员(`double`, `int32_t/64_t`, SIMD)放在结构开头或彼此靠拢,把小字节的成员聚合在一起以避免穿插产生多次填充。始终用 `sizeof` 与 `alignof` 验证你的猜想,必要时用 `static_assert(sizeof(...) == expected)` 把假设编码到编译期。 - -最后,对于对象的拷贝语义:**只有当类型是 `trivially_copyable` 时,才安全地把其二进制拷贝到另一个对象(如 `memcpy(&dst, &src, sizeof T)`)**。不要对含虚函数、含非平凡析构或含特殊成员的类作二进制拷贝;对于这些类型,使用构造/拷贝/赋值语义。 - ------- - -## 在线运行 - -在线体验内存对齐与填充、type traits 判断以及 C++20 指定初始化器: - - - -## 小结 - -- `alignof` 决定对象对齐要求;`sizeof` 报对象在内存中真正占用多少(包含填充)。 -- 对象内部的填充(padding)来自对齐规则;合理安排成员顺序可以减少填充并节省 RAM。 -- `trivial`、`trivially_copyable`、`standard-layout` 是标准对类型特性做的精细划分: - - 想用 `memcpy` 或保存二进制映像,请确保 `trivially_copyable`。 - - 想确保与 C 的布局兼容,请确保 `standard-layout`。 - - `POD` 在概念上就是既 `trivial` 又 `standard-layout`。 -- 聚合初始化很方便;C++20 的指定初始化让初始化更安全、更可读、更不依赖成员顺序。 -- 在嵌入式/底层场景,至少在接口处 `static_assert` 检查这些不变量(大小、对齐、是否 trivially_copyable/standard-layout),这样构建起来的代码既高效又健壮。 diff --git a/documents/vol3-standard-library/06-custom-allocators.md b/documents/vol3-standard-library/06-custom-allocators.md deleted file mode 100644 index 09de5cf4e..000000000 --- a/documents/vol3-standard-library/06-custom-allocators.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -chapter: 7 -cpp_standard: -- 11 -- 14 -- 17 -- 20 -description: 自定义STL分配器 -difficulty: intermediate -order: 6 -platform: host -prerequisites: -- 'Chapter 6: RAII与智能指针' -reading_time_minutes: 8 -tags: -- cpp-modern -- host -- intermediate -title: 自定义分配器 ---- -# 嵌入式现代C++教程——自定义分配器(Allocator) - -在嵌入式世界里,内存不是"无限"的抽屉,而是那只随时会嫌你占空间的行李箱。默认的 `new` / `malloc` 对我们友好吗?有时候很友好(没错,方便);但更多时候,它们是潜在的性能炸弹、不可预测的延迟来源、以及碎片化萌生地。于是,写一个"自定义分配器"——你自己的内存管理策略,就变成了工程师的基本修行。 - ------- - -## 为什么要自定义分配器? - -想象一下这些场景:实时任务不能被偶发的 `malloc` 阻塞;启动阶段需要一次性分配若干对象以避免运行时分配;小对象分配频繁但大小恒定;或者你想把一大块内存划分给特定模块,便于追踪与回收。默认分配器往往无法同时满足:确定性、低内存占用、低碎片和高性能。 - -自定义分配器修改了像内存申请的具体模式,我们可以接入自己的固定大小池、栈式分配、快速分配器。在之前的博客中,我们的这些实现可以有效的避免堆碎片、提高局部性。 - ------- - -## 分配器的基础概念 - -分配器,归根结底就是两件事:**分配**(给出一段未被使用的内存)和**释放**(把内存归回池子)。在 C++ 里还要注意对齐(alignment)和对象的构造/析构(placement `new`、显式 `destroy`)。 - -常见策略有:**Bump(指针上移)分配器**、**Free-list(空闲链表/内存池)**、**Stack(栈)分配器**、以及更复杂的 **TLSF/分级位图** 等。下面我们通过代码直观对比。 - ------- - -## 最简单:Bump(线性)分配器 — 启动与临时用例的好朋友 - -特点:实现极其简单,分配 O(1),不支持释放单个对象(可以一次性重置)。适合启动期分配或者短周期任务。 - -```cpp -// bump_allocator.h - 非线程安全,简单演示 -#include -#include -#include - -class BumpAllocator { - char* start_; - char* ptr_; - char* end_; -public: - BumpAllocator(void* buffer, std::size_t size) - : start_(static_cast(buffer)), ptr_(start_), end_(start_ + size) {} - - void* allocate(std::size_t n, std::size_t align = alignof(std::max_align_t)) noexcept { - std::size_t space = end_ - ptr_; - std::uintptr_t p = reinterpret_cast(ptr_); - std::size_t mis = p % align; - std::size_t offset = mis ? (align - mis) : 0; - if (n + offset > space) return nullptr; - ptr_ += offset; - void* res = ptr_; - ptr_ += n; - return res; - } - - void reset() noexcept { ptr_ = start_; } -}; - -``` - -使用场景:启动时分配所有必要对象,后面不再释放;或临时缓冲池。记住:不能释放单个对象,除非你支持回滚到某个快照点(可以实现"标记/回滚")。 - ------- - -## 固定大小内存池(Free-list) - -当你有大量相同大小的小对象(例如消息节点、连接对象)时,固定大小内存池非常高效。每个槽(slot)大小固定,释放时把槽 push 回空闲链表。分配/释放都 O(1)。 - -```cpp -// simple_pool.h - 单线程示例 -#include -#include -#include - -class SimpleFixedPool { - struct Node { Node* next; }; - void* buffer_; - Node* free_head_; - std::size_t slot_size_; - std::size_t slot_count_; -public: - SimpleFixedPool(void* buf, std::size_t slot_size, std::size_t count) - : buffer_(buf), free_head_(nullptr), slot_size_((slot_size < sizeof(Node*))? sizeof(Node*): slot_size), slot_count_(count) { - // 初始化空闲链表 - char* p = static_cast(buffer_); - for (std::size_t i = 0; i < slot_count_; ++i) { - Node* n = reinterpret_cast(p + i * slot_size_); - n->next = free_head_; - free_head_ = n; - } - } - void* allocate() noexcept { - if (!free_head_) return nullptr; - Node* n = free_head_; - free_head_ = n->next; - return n; - } - void deallocate(void* p) noexcept { - Node* n = static_cast(p); - n->next = free_head_; - free_head_ = n; - } -}; - -``` - -要点提示:`slot_size` 应包含对齐与控制信息;线程安全时需要加锁或使用 lock-free 结构(复杂度上升)。内存利用率高,碎片少。 - ------- - -## Stack(栈)分配器 — LIFO 场景的神器 - -当你分配/释放呈 LIFO(后进先出)模式时,栈分配器速度最快,可以释放一系列分配到某个"标记"为止。 - -```cpp -// stack_allocator.h - 支持标记回滚 -class StackAllocator { - char* start_; - char* top_; - char* end_; -public: - StackAllocator(void* buf, std::size_t size) : start_(static_cast(buf)), top_(start_), end_(start_+size) {} - void* allocate(std::size_t n, std::size_t align = alignof(std::max_align_t)) noexcept { - // 类似Bump的对齐处理 - // ... - } - // 标记与回滚API - using Marker = char*; - Marker mark() noexcept { return top_; } - void rollback(Marker m) noexcept { top_ = m; } -}; - -``` - -适用:短生命周期链、任务栈式分配、帧分配(每帧分配,帧结束统一回收)。 - ------- - -## 用 C++ 风格包装(placement new 与析构) - -分配器只提供原始内存;对象的构造/析构工作还是你的任务。示例如下: - -```cpp -#include // placement new - -// allocate memory for T and construct -template -T* construct_with(Alloc& a, Args&&... args) { - void* mem = a.allocate(sizeof(T), alignof(T)); - if (!mem) return nullptr; - return new (mem) T(std::forward(args)...); -} - -// 销毁并归还内存(手动调用析构) -template -void destroy_with(Alloc& a, T* obj) noexcept { - if (!obj) return; - obj->~T(); - a.deallocate(static_cast(obj)); -} - -``` - -重要:在嵌入式中,禁用异常或在异常敏感代码中使用 `noexcept` 的 allocate 是常见实践;因此好多实现返回 `nullptr` 而不是抛异常。 - ------- - -## 如何把自定义分配器和 STL 一起用 - -标准库的 `std::allocator` 接口在老标准中较为笨重。C++17/20 引入了 `std::pmr::memory_resource`(更现代)用于替换默认分配策略。但在嵌入式里往往不启用完整的 ``,于是你可以自己: - -- 为容器写一个简单的 wrapper,内部使用你的池分配节点。 -- 或实现兼容 `std::allocator` 接口的类(需要一堆 typedef 和 `rebind`),然后传给 `std::vector>`。 - -如果构建环境允许,优先考虑 `std::pmr` —— 它语义更清晰,但开销与支持度要看你的平台。 diff --git a/documents/vol3-standard-library/06-map-set-deep-dive.md b/documents/vol3-standard-library/06-map-set-deep-dive.md new file mode 100644 index 000000000..87237a47f --- /dev/null +++ b/documents/vol3-standard-library/06-map-set-deep-dive.md @@ -0,0 +1,337 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从红黑树底层实现讲透 std::map 与 set:O(log n) 复杂度与稳定的迭代器、C++14 透明比较器的异构查找、C++17 + 节点句柄 extract/merge 与改 key 的唯一正道 +difficulty: intermediate +order: 6 +platform: host +prerequisites: +- vector 深入:三指针、扩容与迭代器失效 +reading_time_minutes: 15 +related: +- 容器选择指南 +tags: +- host +- cpp-modern +- intermediate +- map +- 容器 +title: map 与 set 深入:红黑树、异构查找与节点句柄 +--- +# map 与 set 深入:红黑树、异构查找与节点句柄 + +## 家族合影:map、set 和它们的兄弟 + +我们用了无数次的 `std::map` 和 `std::set`,日常也就是 `insert`、`find`、遍历,好像没什么神秘的。但只要你往下扒一层,就会发现这俩底下藏着一棵红黑树,而且标准其实从来没有点名让它用红黑树——是三大标准库实现不约而同选了它。更别说 C++14 给它配了异构查找,C++17 又塞进来一个节点句柄,能让你零拷贝搬家,还能顺带改掉那个本该是 const 的 key。这一篇我们就把 map 和 set 从底层到现代用法一次捋清楚。 + +先认全家族。有序关联容器一共四个亲兄弟,都长在同一棵红黑树上: + +| 容器 | 存什么 | 键是否唯一 | +|------|--------|-----------| +| `map` | key → value 键值对 | 唯一 | +| `multimap` | key → value 键值对 | 可重复 | +| `set` | 只存 key | 唯一 | +| `multiset` | 只存 key | 可重复 | + +map 和 set 的关系其实很简单:set 就是那个把 value 扔掉、只留 key 的 map,底层节点结构、平衡逻辑、迭代器规矩全都一样。所以这一篇我们以 map 为主线往下讲,set 该有的它都有,区别只在"set 不存 value"这一句话。 + +至于和邻居的边界,一句话就够:你要的是「有序 + 对数查找」就用 `map`/`set`(红黑树);要「无序 + 均摊常数查找」就用 `unordered_map`/`unordered_set`(哈希表);要「有序 + 连续存储(cache 友好)」就上 C++23 的 `flat_map`。三条路线各管一档,这一篇只管红黑树这一条。 + +## 底下藏着一棵红黑树:标准没点名,但三家都选了它 + +标准对 map 的要求其实很克制:元素按 key 排好序,查找、插入、删除都是对数复杂度 O(log n)。至于你用什么数据结构达成这个目标,标准说得很模糊——大致是「平衡二叉搜索树」,但没指定具体哪一种。有意思的地方就在这:libstdc++(GCC)、libc++(Clang)、MSVC STL 三家,最后全都选了红黑树。 + +为什么是红黑树,而不是更「严格平衡」的 AVL 树?关键在删除。AVL 树要求左右子树高度差不超过 1,平衡很紧,代价是删除时可能要从底到顶一路旋转,次数难以控制。红黑树松一些,它只保证「最长路径不超过最短路径的两倍」,换来的是插入最多旋转 2 次、删除最多旋转 3 次——旋转次数有明确上限,对频繁增删的 map 来说更划算。 + +红黑树的规矩就那么几条,我们快速过一遍(不用背,理解它凭什么保证 O(log n) 就行): + +- 每个节点非红即黑 +- 根节点是黑的 +- nil 叶子(空哨兵)是黑的 +- 红节点的孩子必须是黑(不能两个红连在一起) +- 从任一节点到它所有叶子节点的路径,经过的黑节点数量相同(这个叫「黑高」) + +最后两条加在一起,效果就是:你没法让一条路径又长又全是红,因为红不能连排,而黑高又必须一致。于是最长的红黑相间路径,顶多是最短纯黑路径的两倍——树高被压在 O(log n),查找自然也是 O(log n)。 + +节点长什么样?和普通二叉搜索树比,就多了一个颜色位和三个指针: + +```cpp +// 红黑树节点的简化骨架(标准库内部实现,各厂细节不同,这里只看结构) +struct TreeNode { + bool is_red; // 颜色位 + TreeNode* parent; // 父节点指针(自底向上调整时要用) + TreeNode* left; + TreeNode* right; + // map 节点这里存 pair;set 节点只存 Key +}; +``` + +那个 parent 指针值得多说一句。普通二叉搜索树查找只往下走,不需要知道父亲;但红黑树插入、删除时要自底向上调整颜色、做旋转,得能回头找父亲,所以节点都带了 parent 指针。这也解释了为什么红黑树节点比普通链表节点「重」——它是三叉的。set 在这里和 map 完全同构,唯一差别是节点负载里有没有那个 Value,所以接下来讲 map 的所有机制,你把 Value 抹掉就是 set。 + +## 复杂度和迭代器失效:和 vector 完全是两套规矩 + +先把复杂度的账算清楚。红黑树高 O(log n),所以查找、插入、删除都是顺着树往下走一趟,再加上可能的旋转(旋转本身是 O(1) 的局部操作)。常用操作的复杂度: + +| 操作 | 复杂度 | +|------|--------| +| `find` / `count` / `contains` / `operator[]` / `at` | O(log n) | +| `insert` / `emplace` / `erase` | O(log n) | +| 有序遍历 | O(n) | + +这里要特别拎出来讲的不是复杂度——红黑树慢点就慢点,很正常——而是**迭代器失效**。map 的失效规矩和 vector 完全是两套,而这恰恰是你在工程里选 map 而不是 vector 的一个硬理由。 + +vector 我们在[那一篇](03-vector-deep-dive.md)讲过:一旦扩容,所有迭代器、引用、指针全部失效,因为底层是连续内存、整体搬迁。map 不一样,它的元素是挂在各自独立的树节点上的: + +- **插入**:不失效任何已有的迭代器、引用、指针 +- **删除**:只失效被删元素本身的那一个迭代器/引用,其他元素纹丝不动 + +这意味着什么?意味着 map 里元素的地址是稳定的。你可以拿着一个指向 map 元素的指针或引用到处传来传去,只要你不删掉它,这个指针就永远有效。哪怕你在 map 里又插了几千个新元素,或者删了几百个别的元素,手里那个指针照样指着原来那个元素。 + +这个性质在工程里非常值钱。比方说你写一个事件注册表,每个回调登记进 map 之后,你想把它的指针交给别的子系统去引用、去注销——如果用 vector,一次扩容就把所有指针打成野指针;用 map 就稳稳当当。 + +我们跑个小例子看一眼这个稳定性: + +```cpp +#include +#include +#include + +int main() +{ + std::map registry; + registry[1] = "alpha"; + registry[2] = "beta"; + + // 拿一个指向元素 1 的引用和迭代器 + std::string& ref = registry.at(1); + auto it = registry.find(1); + + // 狂插一堆新元素,触发多次红黑树重平衡 + for (int i = 100; i < 200; ++i) { + registry[i] = "x"; + } + + // 再删掉一些无关元素 + registry.erase(150); + registry.erase(160); + + // 原来的引用和迭代器还有效吗? + std::cout << "ref = " << ref << '\n'; + std::cout << "it = " << it->second << '\n'; + + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/map_stable /tmp/map_stable.cpp && /tmp/map_stable +``` + +```text +ref = alpha +it = alpha +``` + +不管中间插了多少、删了多少(只要没删元素 1 自己),那个引用和迭代器一直有效。这就是红黑树「节点独立挂在堆上」带来的稳定性,也是 map 区别于 vector 的核心工程价值之一。 + +## 异构查找(C++14):别再为查找造一个临时 string 了 + +下面这个坑,写过 string 键 map 的人多半踩过,只是没意识到。看这段: + +```cpp +std::map scores; +scores["alice"] = 90; + +auto it = scores.find("alice"); // "alice" 是 const char* +``` + +`find` 的签名是 `find(const key_type&)`,key_type 是 `std::string`。可你传进去的是个 `const char*`。于是编译器贴心地帮你用 `"alice"` 构造了一个临时的 `std::string`,再拿这个临时对象去查找。一次查找,白搭一个 string 构造——而且 SSO 装不下的话,这个临时 string 还要去堆上分配内存,查完立刻析构释放。你要是在热路径上高频这么查,开销全花在造临时 string 上了。 + +C++14 给了正解:**透明比较器(transparent comparator)**。 + +默认情况下 map 的比较器是 `std::less`,它只认 string。但标准库还提供了一个特化版本 `std::less`(写成 `std::less<>`),它不绑定具体类型,而是用 `operator<` 直接比较传入的任意两个类型——前提是这俩类型能比。你只要把 map 的比较器声明成 `std::less<>`,它就获得了异构查找能力: + +```cpp +#include +#include +#include + +// 关键:比较器用 std::less<>(透明),而不是默认的 std::less +std::map> scores; +scores["alice"] = 90; + +// 现在这两种查法都不构造临时 string +scores.find("alice"); // const char* 直接比 +scores.find(std::string_view("alice")); // string_view 直接比 +``` + +背后的机制是 `is_transparent` 这个嵌套类型。`std::less<>` 内部 typedef 了一个 `is_transparent`,map 的查找重载看到比较器有这个标记,就启用异构版本,直接拿你给的原生类型去和树里的 string 比。string 和 `const char*`、`string_view` 之间本来就支持比较,所以一路畅通,一个临时对象都不构造。 + +注意两个边界。第一,这要求你的 key 类型和查找类型之间能直接比较——string 和 `const char*` 能比,但你自定义的类型 key 如果没提供和 `string_view` 的比较,就享受不到。第二,异构查找主要在 `find`、`count`、`contains` 这些查找类操作上生效。省临时对象是真的,但「省了就更快」却未必——查找类型用 `const char*` 反而可能更慢(它没有长度缓存,红黑树多次比较里要反复 strlen),得用 `string_view` 才真正提速,这点我们待会儿跑给你看。 + +## extract 和 merge(C++17):节点句柄,搬家还能顺便改个 key + +C++17 给关联容器塞进来一个叫「节点句柄(node handle)」的东西,听名字挺玄,其实解决的是三个很实在的问题。 + +先看节点句柄是什么。map 从 C++11 起就有个规矩:key 是 const 的,你拿到一个 map 元素,没法直接改它的 key——`m.begin()->first = 100` 这种写法编译都过不了(key 那个 `first` 是 `const`)。原因也好理解:map 靠 key 排序维持红黑树结构,你要是能随便改 key,树的有序性当场就崩了。 + +节点句柄绕开了这个限制。`extract` 能把一个节点从树里整个「摘」下来,返回一个独立的节点句柄(类型是 `std::map::node_type`)。这个句柄持有节点的所有权,既不在任何 map 里(摘走不影响别的元素),也不拷贝 value——它就是原来那个节点本体。摘下来之后,你可以改它的 key(因为这时候它已经脱离了树,改 key 不会破坏任何有序性),然后再 `insert` 回去。 + +所以「改 map 元素的 key」这件事,从 C++17 起有了唯一合法的正道:**extract → 改 key → insert**。 + +```cpp +#include +#include +#include + +int main() +{ + std::map m; + m[1] = "alpha"; + + // 直接改 key 编译不过(map 的 key 是 const) + // m.begin()->first = 100; + + // 正确做法:extract 摘节点,改 key,再 insert + auto node = m.extract(1); // 摘下 key=1 的节点 + node.key() = 100; // 现在能改 key 了(节点已脱离树) + m.insert(std::move(node)); // 插回去,新 key=100 + + std::cout << "count(1) = " << m.count(1) << '\n'; + std::cout << "count(100) = " << m.count(100) << '\n'; + std::cout << "value = " << m.at(100) << '\n'; + + return 0; +} +``` + +```bash +g++ -std=c++17 -O2 -o /tmp/map_extract /tmp/map_extract.cpp && /tmp/map_extract +``` + +```text +count(1) = 0 +count(100) = 1 +value = alpha +``` + +注意看 value 还是 "alpha"——整个过程中 value 一次都没被拷贝或移动,搬的就是原来那个节点。这就是「零拷贝搬家」。 + +第二个用途是跨容器迁移节点。两个 map,你想把一个里的某些节点挪到另一个,`extract` + `insert` 就行,同样不拷贝 value: + +```cpp +std::map a, b; +a[1] = "x"; +a[2] = "y"; + +// 把 a 里的节点 1 整个搬到 b +auto node = a.extract(1); +b.insert(std::move(node)); +``` + +第三个用途是 `merge`,一把梭。`m1.merge(m2)` 会把 m2 里所有 key 在 m1 不冲突的节点,整个搬进 m1,同样零拷贝: + +```cpp +std::map m1{{1, "a"}, {2, "b"}}; +std::map m2{{2, "dup"}, {3, "c"}}; + +m1.merge(m2); +// m1: {1, 2, 3};m2 里只剩下 key=2 那个(因为 m1 已有 2,冲突没搬走) +``` + +`merge` 的复杂度是 O(n·log n)(n 是被搬的数量),但全程没有 value 的拷贝——这在迁移大对象(比如 value 是个大 vector 或长字符串)时,省下的开销非常实在。 + +## 透明比较器到底快不快?跑跑看 + +先说个题外的事实:libstdc++、libc++、MSVC STL 三家的 map 底层都是红黑树,行为完全一致(这是标准强制的),只是节点布局、内存分配的细节各有各的做法。日常工程不用纠结,知道「行为一致、实现各异」就够了。 + +但有个更值得亲自验证的问题:透明比较器号称省了临时对象,那它到底快不快?很多人(包括写这篇之前的我)会想当然觉得「省了构造肯定更快」。咱们别猜,直接跑跑看。 + +准备一个 string 键的 map,key 用长字符串(44 字符,超过 SSO、临时构造要走堆),然后对比三种查法:A 是默认比较器用 `const char*` 查(会构造临时 string);B 是透明比较器用 `const char*` 查;C 是透明比较器用 `string_view` 查。 + +```cpp +#include +#include +#include +#include +#include + +int main() +{ + std::map classic; + std::map> transparent; + for (int i = 0; i < 10000; ++i) { + std::string k(40, 'a'); + k += std::to_string(i); + classic[k] = i; + transparent[k] = i; + } + std::string needle_str(40, 'a'); + needle_str += "9999"; + const char* needle = needle_str.c_str(); + std::string_view needle_sv(needle); + volatile int sink = 0; + + auto bench = [&](auto fn) { + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < 100000; ++i) { + sink += fn()->second; + } + auto t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration(t1 - t0).count(); + }; + + std::cout << "A classic find(const char*): " + << bench([&] { return classic.find(needle); }) << " ms\n"; + std::cout << "B transparent find(const char*): " + << bench([&] { return transparent.find(needle); }) << " ms\n"; + std::cout << "C transparent find(string_view): " + << bench([&] { return transparent.find(needle_sv); }) << " ms\n"; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/map_bench3 /tmp/map_bench3.cpp && /tmp/map_bench3 +``` + +```text +A classic find(const char*): 10.5 ms +B transparent find(const char*): 15.5 ms +C transparent find(string_view): 8.7 ms +``` + +(GCC 16.1.1,本机;具体毫秒数随你的机器变化,但三者的大小关系稳定。) + +结果大概率跟你的直觉相反——**B 反而最慢**,C 最快。为什么?关键在 `const char*` 没缓存长度。红黑树一次查找要比较 log(n) 次(这里约 14 次),B 每次拿裸 `const char*` 跟树里的 string 比,都要从头扫到 `'\0'` 算长度(`strlen`),14 次比较就是 14 次 strlen;而 A 虽然先花一次构造临时 string(走堆),但之后那 14 次比较都是 string 对 string,直接用各自缓存的长度做 `memcmp`,反而更快。C 用 `string_view`,构造时算一次长度并缓存下来,后面比较都复用这个长度,既不用每次 strlen、又不构造临时 string,所以最快。 + +所以记住这个容易踩的坑:**透明比较器要配 `string_view` 才真正提速,配 `const char*` 反而可能更慢**。光是把 `std::less<>` 摆上去、查找类型却用错,性能不升反降。 + +## 临了收几句 + +map 和 set 这一家子,表面上看就是「能按键排序、能 O(log n) 查」的容器,底下却是一棵三大实现不约而同选中的红黑树。把它的几个关键性质记牢,以后用 map 心里就有底了:元素地址稳定(插入不失效、删除只失效被删的那个),所以适合做需要稳定句柄的注册表、观察者一类的结构;C++14 的透明比较器让你查 string 键 map 时不再白造临时对象(但记得配 `string_view` 查找才真正提速,用 `const char*` 反而更慢);C++17 的节点句柄给了你零拷贝搬家和改 key 的唯一合法通道。set 呢,就是把同一套机制里 value 抹掉的那个版本,所有规矩照搬。 + +下一篇我们顺着这条线,去看 map 的「无序兄弟」`unordered_map`——红黑树的对数查找,换成哈希表的均摊常数查找,是另一种完全不同的取舍。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::map — cppreference](https://en.cppreference.com/w/cpp/container/map) +- [std::set — cppreference](https://en.cppreference.com/w/cpp/container/set) +- [std::less\ 透明比较器 — cppreference](https://en.cppreference.com/w/cpp/utility/functional/less_void) +- [map::extract / merge 节点句柄 — cppreference](https://en.cppreference.com/w/cpp/container/map/extract) +- [容器迭代器失效规则总表 — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) +- [N3657:C++14 异构查找提案](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3657.htm) diff --git a/documents/vol3-standard-library/07-unordered-map-set-deep-dive.md b/documents/vol3-standard-library/07-unordered-map-set-deep-dive.md new file mode 100644 index 000000000..6cca4cae8 --- /dev/null +++ b/documents/vol3-standard-library/07-unordered-map-set-deep-dive.md @@ -0,0 +1,264 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从哈希表底层讲透 std::unordered_map/set:桶与链地址法、装填因子与 rehash、平均 O(1) 与最坏 O(n)、自定义 + hash 的写法、C++14 起 rehash 不失效引用,以及与 map 的选择决策 +difficulty: intermediate +order: 7 +platform: host +prerequisites: +- map 与 set 深入:红黑树、异构查找与节点句柄 +reading_time_minutes: 10 +related: +- 容器选择指南 +tags: +- host +- cpp-modern +- intermediate +- unordered_map +- 容器 +title: unordered_map 与 unordered_set 深入:哈希表、桶与自定义 hash +--- +# unordered_map 与 unordered_set 深入:哈希表、桶与自定义 hash + +## 和 map 是亲戚,但底层换了个世界 + +上一篇我们讲 map,它底下一棵红黑树,查找是对数 O(log n)。这一篇的 `unordered_map`,名字里带个 "unordered"——它不排序,代价换来了更狠的东西:平均 O(1) 的查找。但天底下没有免费的午餐,O(1) 的代价是底层从一棵树换成了一张哈希表,引出桶、装填因子、rehash、自定义 hash 这一整套新的机制。我们这一篇就把 `unordered_map` 和 `unordered_set` 从哈希表底层到工程用法讲透。 + +先把它和 map 放一起看,差异一眼就清楚: + +| | `map` / `set` | `unordered_map` / `unordered_set` | +|---|---|---| +| 底层 | 红黑树 | 哈希表 | +| 有序 | 是(按键排序) | 否 | +| 查找/插入/删除 | O(log n) | 平均 O(1),最坏 O(n) | +| 自定义键需要 | `operator<` | hash + `operator==` | +| 插入是否失效迭代器 | 否 | 可能(触发 rehash 时) | + +一句话:你要有序遍历、或需要「前驱/后继」这类范围操作,就留在 map;你要的就是纯查找、插入、删除,不在乎顺序,`unordered` 多半更快。这个选择不是绝对的,后面我们会细说。 + +## 底层是一张哈希表:桶、链表与装填因子 + +`unordered_map` 底下是一张哈希表,绝大多数实现用的是**链地址法(separate chaining)**:一个 bucket 数组,每个桶挂一条链表(或类似结构)。插入一个元素时,先用 hash 函数算出 key 的哈希值,再对桶数量取模,决定它落到哪个桶;这个桶里已经有元素就挂在链表后面,查找时就在这条短链上线性扫。 + +```cpp +// 链地址法哈希表的简化骨架(标准库内部,各厂细节不同) +struct HashTable { + std::vector buckets; // bucket 数组,每个桶内部是同 hash 元素的链表 +}; +// 插入/查找定位:bucket_index = hash(key) % buckets.size(); +``` + +这里有个关键概念:**装填因子(load factor)**。它等于 `size() / bucket_count()`,也就是平均每个桶挂了多少个元素。桶越挤,链表越长,查找就越慢。标准库设了一个上限 `max_load_factor()`,默认是 1.0——当装填因子超过这个上限,容器就 **rehash**:分配一个更大的 bucket 数组(通常扩到大约两倍),把所有元素重新 hash、重新落桶。 + +rehash 是 `unordered_map` 最贵的操作:它要把全部元素重新搬一遍,复杂度 O(n)。虽然均摊到每次插入上仍是常数,但单次 rehash 那一下会有明显的停顿。这也是为什么工程里如果你能预估元素数量,最好在插入之前先 `reserve(n)`——它一次性把 bucket 开够,避免后续反复 rehash。 + +```cpp +std::unordered_map m; +m.reserve(10000); // 提前开好桶,避免逐个插入时的多次 rehash +``` + +我们跑一下,看 load_factor 怎么触发 rehash: + +```cpp +#include +#include + +int main() +{ + std::unordered_map m; + std::size_t prev = m.bucket_count(); + std::cout << "初始 bucket_count = " << prev << "\n"; + for (int i = 0; i < 100; ++i) { + m[i] = i; + if (m.bucket_count() != prev) { + std::cout << "size=" << m.size() + << " rehash: " << prev << " -> " << m.bucket_count() + << " (load_factor=" << m.load_factor() << ")\n"; + prev = m.bucket_count(); + } + } + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/lf_rehash /tmp/lf_rehash.cpp && /tmp/lf_rehash +``` + +```text +初始 bucket_count = 1 +size=1 rehash: 1 -> 13 (load_factor=0.0769231) +size=14 rehash: 13 -> 29 (load_factor=0.482759) +size=30 rehash: 29 -> 59 (load_factor=0.508475) +size=60 rehash: 59 -> 127 (load_factor=0.472441) +``` + +注意看 bucket_count 的跳变序列:1 → 13 → 29 → 59 → 127,**全是质数**——这正是 libstdc++ 的选择(用质数桶能让 `hash % bucket_count` 的分布更均匀)。而每次跳变都发生在 `size` 刚刚超过 `bucket_count`(也就是 load_factor 突破 1.0)那一刻:size 到 14 时 14/13 > 1.0 触发扩到 29,size 到 30 时 30/29 > 1.0 触发扩到 59,依此类推。这就是「装填因子超限 → rehash 扩桶」的直观过程。 + +## 复杂度与迭代器失效:和 map 又不一样了 + +复杂度先说清楚:`unordered_map` 的查找、插入、删除在**平均情况**下是 O(1),**最坏情况**是 O(n)。最坏情况什么时候发生?当大量 key 哈希冲突(都落到同一个桶),哈希表退化成一条长链表,查找变成线性扫描。好的 hash 函数加上合理的 load factor 能让冲突概率极低,所以实践中几乎总是 O(1);但标准诚实地标注了最坏 O(n),因为理论上它确实可能。 + +迭代器失效这块,`unordered_map` 和 map 又不一样,而且比 map 更「凶」一点。规则是: + +- **rehash**(插入触发,或手动 `reserve` / `rehash`):**失效所有迭代器**;但 C++14 起,**指向元素的引用和指针不被 rehash 失效** +- **erase**:只失效被删元素本身的迭代器/引用,其他不受影响 + +这条要特别留意。上一篇我们说过 map 插入绝不失效迭代器;`unordered_map` 因为插入可能 rehash,迭代器会失效。但有意思的是,C++14 之后标准额外保证了 rehash 不动指向元素的引用和指针——也就是说,你持有的 `value_type&` 和元素指针在 rehash 后仍然有效,只有迭代器会废。这是个实用的保证:你可以安全地长期持有 `unordered_map` 元素的引用,即便中间发生了 rehash。 + +```cpp +#include +#include +#include + +int main() +{ + std::unordered_map m; + m[1] = "alpha"; + std::string& ref = m.at(1); // 持有元素引用 + + m.reserve(1000); // 触发 rehash,迭代器全失效 + for (int i = 100; i < 200; ++i) { + m[i] = "x"; // 大量插入可能再次 rehash + } + + std::cout << ref << '\n'; // C++14 起,引用仍然有效 + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/umap_ref /tmp/umap_ref.cpp && /tmp/umap_ref +``` + +```text +alpha +``` + +## 自定义 hash:让自定义类型也能当 key + +默认情况下,`std::hash` 只对内置类型和标准库常用类型(string、整数类型等)有定义。你想拿自定义类型当 `unordered_map` 的 key,就得告诉它两件事:**怎么算 hash**、**怎么判等**。 + +判等默认用 `operator==`(通过 `std::equal_to`)。hash 有两种给法:特化 `std::hash`,或者直接把一个自定义 Hash 类型作为模板参数传给 `unordered_map`。我们看一个把二维点当 key 的例子,这里用特化 `std::hash` 的写法: + +```cpp +#include +#include + +struct Point { + int x, y; + bool operator==(Point const& o) const { return x == o.x && y == o.y; } +}; + +// 特化 std::hash +namespace std { +template <> +struct hash { + std::size_t operator()(Point const& p) const noexcept + { + // 把两个 int 组合成一个 size_t;这是简化版,生产里用更好的混合 + return static_cast(p.x) * 31 + static_cast(p.y); + } +}; +} // namespace std + +int main() +{ + std::unordered_map grid; + grid[{1, 2}] = "A"; + grid[{3, 4}] = "B"; + + auto it = grid.find({1, 2}); + std::cout << (it != grid.end() ? it->second : "not found") << '\n'; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/custom_hash /tmp/custom_hash.cpp && /tmp/custom_hash +``` + +```text +A +``` + +这里有个铁律:**hash 和 `==` 必须一致**。也就是说,如果 `a == b` 为真,那 `hash(a)` 必须等于 `hash(b)`——否则相等的元素会落到不同的桶,查找就找不到了。反过来不要求(`hash(a) == hash(b)` 时 `a` 不必等于 `b`,那只是冲突,正常现象)。上面这个 `x*31 + y` 是演示用的简单混合,生产环境可以用 `boost::hash_combine` 或更讲究的混合函数,进一步降低冲突概率。 + +## 哈希冲突与 DoS:libstdc++ 给你的 hash 为什么带了点随机 + +哈希表有个著名的攻击面叫 **hash flooding**:攻击者精心构造一大批哈希值相同的 key 喂给你的程序,所有元素挤进同一个桶,查找从 O(1) 退化成 O(n),CPU 被打满——这是早年很多 web 服务被拖垮的原因之一。 + +libstdc++ 的应对是:它的 `std::hash` 在每次程序启动时用一个随机种子做 hash(基于带种子的高质量 hash 函数)。这样一来,同一份输入在不同进程里落桶的位置不一样,攻击者没法预先构造出「刚好全冲突」的输入。这是 libstdc++ 的实现策略(libc++、MSVC STL 各有各的做法),标准并不强制——但实战里这是个值得知道的事:如果你用自定义类型 key,且 key 可能来自不可信输入,你写的 hash 函数质量就直接关系到抗 DoS 的能力。 + +## 上手跑一跑:unordered_map 比 map 快多少 + +光说「平均 O(1) 比 O(log n) 快」太抽象,我们直接量一下。准备十万元素的 map 和 unordered_map,各做一百万次查找: + +```cpp +#include +#include +#include +#include + +int main() +{ + std::map om; + std::unordered_map um; + for (int i = 0; i < 100000; ++i) { + om[i] = i; + um[i] = i; + } + volatile int sink = 0; + + auto bench = [&](auto& m) { + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < 1000000; ++i) { + sink += m.find(i % 100000)->second; + } + auto t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration(t1 - t0).count(); + }; + + std::cout << "map: " << bench(om) << " ms\n"; + std::cout << "unordered_map: " << bench(um) << " ms\n"; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/uvm /tmp/uvm.cpp && /tmp/uvm +``` + +```text +map: 48.4 ms +unordered_map: 2.2 ms +``` + +上面是 GCC 16.1.1 在本机跑的结果:map 约 48ms,unordered_map 约 2ms,**unordered 快了将近一个数量级**。具体毫秒数随你的机器变化,但这个量级差距是稳定的——十万元素下,map 一次查找要 log₂(100000) ≈ 17 次比较,unordered_map 平均 O(1) 直接命中,百万次查找累积出来的差距就是这么明显。这就是 `unordered_map` 存在的核心理由。 + +## 临了收几句:什么时候该选它 + +`unordered_map` 和 `unordered_set` 把「有序」这个属性丢掉,换来了平均 O(1) 的查找。它的底层是哈希表——bucket 数组加每桶一条链,靠装填因子控制何时 rehash 扩容。用它要记住几件事:插入可能触发 rehash,这会失效迭代器,但 C++14 起不失效指向元素的引用;自定义类型当 key 必须提供 hash 和 `==`,且两者必须一致;如果 key 来自不可信输入,hash 函数的质量关系到抗冲突 DoS 的能力。 + +至于什么时候选它而不是 map:不在乎顺序、且以查找/插入/删除为主,`unordered` 多半更快;需要有序遍历、范围查询、或稳定的迭代器顺序,就回到 map。下一篇我们离开关联容器,去看顺序容器里 vector 之外的选择——deque 和 list。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::unordered_map — cppreference](https://en.cppreference.com/w/cpp/container/unordered_map) +- [std::unordered_set — cppreference](https://en.cppreference.com/w/cpp/container/unordered_set) +- [std::hash — cppreference](https://en.cppreference.com/w/cpp/utility/hash) +- [容器迭代器失效规则总表 — cppreference](https://en.cppreference.com/w/cpp/container#Iterator_invalidation) diff --git a/documents/vol3-standard-library/08-span.md b/documents/vol3-standard-library/08-span.md new file mode 100644 index 000000000..3ce21c328 --- /dev/null +++ b/documents/vol3-standard-library/08-span.md @@ -0,0 +1,187 @@ +--- +chapter: 7 +cpp_standard: +- 17 +- 20 +description: 讲透 std::span:指针加长度的非拥有视图、动态与静态 extent 的内存差异、统一接收 array/vector/C 数组、零拷贝切片 + subspan、字节视图 as_bytes,以及悬垂视图的生命周期陷阱 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 7 +related: +- array:编译期固定大小的聚合容器 +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- intermediate +- span +- 容器 +title: span:非拥有的连续视图 +--- +# span:非拥有的连续视图 + +## span 是什么:一个指针加一个长度,仅此而已 + +`std::span` 是 C++20 给「一段连续数据」配的标准化视图。它不拥有这段内存,只持有两样东西:一个指针,一个长度。就这么简单——你可以把它理解成一个「带边界信息的指针」,或者 C 里 `(ptr, len)` 这对参数的正式封装。它不分配、不释放、不拷贝底层数据,拷贝一个 span 就是拷贝那两个字(指针和 size),极其廉价。 + +```cpp +std::vector v = {1, 2, 3, 4}; +std::span s(v); // s 指向 v 的数据,但不拥有 +s.size(); // 4 +s[0]; // 1 +s.data() == v.data(); // true +``` + +它的核心价值在「传参」:函数想接受「一段 T 数据」时,用 `std::span` 能统一接收 C 数组、`std::array`、`std::vector`、`(指针, 长度)` 等所有连续来源,既不拷贝数据,也不用把函数写成模板。 + +## 为什么需要它:指针+长度传参的老毛病 + +C/C++ 里传「一段内存」给函数,老办法是 `void f(T* ptr, std::size_t n)`。这招能跑,但毛病不少:长度 `n` 的单位是元素还是字节得靠注释或猜;函数会不会修改数据看 `T*` 还是 `const T*`,容易漏;调用方传错长度没有任何编译期保护;而且这俩参数得成对传、成对记。span 把指针和长度打包进一个对象,类型(`span` vs `span`)直接表达只读/可写意图,长度跟着对象走,丢不了。 + +```cpp +// 老办法:长度单位、只读与否全靠注释 +void process_old(const uint8_t* buf, std::size_t n); + +// span 办法:类型即语义 +void process(std::span buf); // 明确:只读,长度内建 +void mutate(std::span buf); // 明确:会改,长度内建 +``` + +这比写 `template void process(const C& c)` 也更省事——不用为每种容器实例化一份,避免编译膨胀。 + +## 动态 extent 与静态 extent + +span 有两种形态,区别在「长度是运行时存还是编译期定」。`std::span`(完整写法 `std::span`)是**动态 extent**:长度作为成员存着,运行时任意;`std::span` 是**静态 extent**:长度 `N` 编译期定死,不在对象里存。 + +这个区别会直接体现在 `sizeof` 上——咱们待会儿跑跑看。动态 extent 要存指针 + size(两个字),静态 extent 只存指针(size 编译期已知,省掉)。日常里动态 extent 更常用(数据长度往往运行时才定),静态 extent 适合「我知道就是 N 个」的场合,能省一个字的存储,还能换来一点编译期检查。 + +```cpp +int arr[4]; +std::span s_fixed(arr); // 只能绑长度 4 的数据 +std::span s_dyn(arr); // 任意长度,运行时记 4 +``` + +## 接收任意连续来源:array / vector / C 数组 / 指针+长度 + +span 的构造函数覆盖了几乎所有连续数据来源,这让函数参数用 `span` 能一统江湖: + +```cpp +void print(std::span s); + +int buf[] = {0x10, 0x20, 0x30}; +std::array a = {1, 2, 3}; +std::vector v = {4, 5, 6, 7}; +int* p = v.data(); + +print(buf); // C 数组(自动推 N) +print(a); // std::array +print(v); // std::vector +print({p, 2}); // 指针 + 长度 +``` + +调用方不用拷贝数据,函数内部也不用为每种容器写重载或模板。注意 `span` 表示只读视图——如果函数要改数据,用 `span`(非 const)。 + +## subspan、first、last:零拷贝切片 + +span 提供 `subspan(offset, count)`、`first(n)`、`last(n)` 三件套,返回的是新的 span(还是非拥有视图),不拷贝任何数据。这在协议解析、缓冲区处理里特别顺手——把一个大 buffer 切成 header / payload,各自当 span 传下去: + +```cpp +void recv_packet(std::span buffer) +{ + if (buffer.size() < 4) { + return; + } + auto header = buffer.first(4); // 前 4 字节视图 + uint16_t len = static_cast(header[2] | (header[3] << 8)); + if (buffer.size() < 4 + len) { + return; + } + auto payload = buffer.subspan(4, len); // 跳过 header 取 payload 视图 + // payload 仍是非拥有视图,零拷贝 +} +``` + +整个过程中没有任何字节被拷贝,切出来的 header / payload 都指向原 buffer 内部。 + +## 字节视图:as_bytes / as_writable_bytes + +处理二进制数据时,常需要把 `span` 当成原始字节看。`std::as_bytes(s)` 返回 `span`,`std::as_writable_bytes(s)` 返回 `span`(仅当 T 非 const 时可用)。这对 CRC、序列化、内存 dump 这类「把结构当字节流」的场景很合适: + +```cpp +std::span data = /* ... */; +auto bytes = std::as_bytes(data); // span,只读字节 +// crc(bytes.data(), bytes.size()); +``` + +注意区分只读和可写:读用 `as_bytes`,要原地改字节用 `as_writable_bytes`(且底层 span 必须 non-const)。 + +## 生命周期:span 不拥有,悬垂会咬人 + +span 最大的坑,也是它「非拥有」性质的必然代价:**它不管理底层内存的生命周期**。底层活多久,span 就最多活多久;底层没了,span 就是悬垂视图,访问就是未定义行为。最经典的错误是 span 绑了一个临时对象,然后把它返回出去: + +```cpp +std::span bad() +{ + std::vector v = {1, 2, 3}; + return v; // v 在函数结束时销毁,返回的 span 立刻悬垂 +} +``` + +调用方拿到这个 span 再访问,就是访问已释放内存。记住这条铁律:**span 的生命周期不得超过它所指向的数据**。只要你不把 span 绑到临时量、不把它存得比底层数据久,它就是安全的。 + +## 跑跑看:动态 vs 静态 extent 的 sizeof + +前面说动态 extent 存两个字、静态 extent 只存指针,咱们跑跑看: + +```cpp +#include +#include + +int main() +{ + int arr[4] = {}; + std::span dyn; // 动态 extent:可默认构造(空 span) + std::span fixed(arr); // 静态 extent:必须绑定数据 + std::cout << "sizeof(span) = " << sizeof(dyn) << '\n'; + std::cout << "sizeof(span) = " << sizeof(fixed) << '\n'; + std::cout << "sizeof(void*) = " << sizeof(void*) << '\n'; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/span_sizeof /tmp/span_sizeof.cpp && /tmp/span_sizeof +``` + +```text +sizeof(span) = 16 +sizeof(span) = 8 +sizeof(void*) = 8 +``` + +(64 位平台,GCC 16.1.1。)动态 extent 是 16 字节(一个 8 字节指针 + 一个 8 字节 size),静态 extent 只有 8 字节(就一个指针,size 编译期已知,省掉了)。这就是静态 extent 的存储优势——在大量传递 span 的场景(比如嵌入式里满地都是的 buffer 视图),省一半的字是有意义的。 + +## 延伸:嵌入式里的 span(DMA / 协议解析) + +span 因为轻量、零拷贝、跨容器统一,在嵌入式里几乎是「现代版 buffer 指针」,这里补几个实战用法(主线之外,按需取用)。DMA 回调把数据放进固定 buffer 后,用 span 切片解析 header / payload,无需拷贝;从 Flash 读数据到缓冲区,用 span 切块处理;中断 / 实时路径里传小段数据,span 拷贝廉价(就两个字)。只要守住「span 不拥有、不超底层生命周期」这条线,它就是裸指针的安全替代。 + +## 临了收几句:span 和 string_view 怎么分 + +span 和 string_view 都是「非拥有视图」,分界看元素类型:`span` 通用于任意元素类型(包括可写、包括 `std::byte`),`string_view` 专门给字符序列(只读、带字符串语义)。处理二进制 buffer / 任意类型数据用 span,处理文本用 string_view。一句话记 span:它是指针加长度的正式封装,传参统一、切片零拷贝,但你得自己管好生命周期。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::span — cppreference](https://en.cppreference.com/w/cpp/container/span) +- [std::byte — cppreference](https://en.cppreference.com/w/cpp/types/byte) +- [P0122 span 提案 — open-std](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0122r7.pdf) diff --git a/documents/vol3-standard-library/09-container-adapters.md b/documents/vol3-standard-library/09-container-adapters.md new file mode 100644 index 000000000..185d8bf92 --- /dev/null +++ b/documents/vol3-standard-library/09-container-adapters.md @@ -0,0 +1,162 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 20 +- 23 +description: 讲透三个容器适配器:它们不是新容器,而是给底层容器套上受限接口拼出 LIFO/FIFO/堆语义;priority_queue 的本质是底层容器加 + std::push_heap/pop_heap,默认最大堆、换比较器变最小堆,外加 C++23 的 push_range +difficulty: intermediate +order: 9 +platform: host +prerequisites: +- vector 深入:三指针、扩容与迭代器失效 +- deque、list 与 forward_list:vector 之外的三个选择 +reading_time_minutes: 8 +related: +- 容器选择指南:按操作、内存与失效规则挑对容器 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: 容器适配器:stack、queue、priority_queue 是怎么「包」出来的 +--- +# 容器适配器:stack、queue、priority_queue 是怎么「包」出来的 + +## 适配器不是容器:是给底层容器套个受限外壳 + +`stack`、`queue`、`priority_queue` 这三个,标准里叫**容器适配器(container adaptor)**,不是独立容器。区别在于:一个真正的容器(比如 `vector`、`deque`)自己持有数据、自己决定存储方式;而适配器自己不发明存储,它**持有一个底层容器(underlying container)**,然后在它外面套一层受限的接口,只让你按某一种特定方式(栈、队列、优先队列)去访问数据。 + +这个「受限」是关键,也是适配器存在的理由。`std::stack` 只暴露 `top`/`push`/`pop`,全部发生在同一端,物理上不可能从中间偷一个元素出来——这就把「后进先出」从约定变成了结构保证,编译器层面就帮你拦住了误用。同理 `queue` 保证先进先出、`priority_queue` 保证你永远拿到当前最优先的那个。代价是你失去了随意访问的能力,但换回来的是「拿到的元素类型可预测、接口不会被滥用」。所以选不选用适配器,本质是问自己:**我是不是只想用这一种访问模式,并希望类型系统替我挡住其它操作?** + +## stack 与 queue:用末端的几个操作拼出 LIFO/FIFO + +适配器的接口就是底层容器几个操作的重新命名。`std::stack` 是后进先出,`push` 把元素压到末端、`top` 看末端、`pop` 弹末端——三个动作全发生在容器的 `back` 这一端,所以它对底层容器的要求是 `back()` / `push_back()` / `pop_back()`。`std::queue` 是先进先出,`push` 从 `back` 进、`front()`/`pop` 从 `front` 出,于是它额外要求底层容器有 `front()` 和 `pop_front()`。 + +| 适配器 | 语义 | 要求底层容器支持 | 默认底层 | +|--------|------|----------------|---------| +| `stack` | LIFO | `back`、`push_back`、`pop_back` | `deque` | +| `queue` | FIFO | `front`、`back`、`push_back`、`pop_front` | `deque` | +| `priority_queue` | 优先级 | `front`、`push_back`、`pop_back` + **随机访问迭代器** | `vector` | + +默认底层为什么是 `deque`?因为它两端插删都是 O(1),对 stack(只用 back)和 queue(用 front+back)都刚好满足,而且 `deque` 没有 vector 那种扩容时整块搬移的代价。这里有个反直觉的点值得记一下:**`std::queue` 没法用 `vector` 当底层**,因为 vector 没有 `pop_front`——要从 vector 头部弹出只能 erase(begin()),那是 O(n) 且标准库压根没提供这个成员,硬塞会编译失败。要给 queue 换底层,合法选择只有 `deque` 和 `list`。`stack` 就宽裕得多,`vector`/`deque`/`list` 都行,因为三个要求它都满足。 + +## priority_queue:底层容器加堆算法,这才是重点 + +三个适配器里 `priority_queue` 最值得拆,因为它的实现最能体现「适配器 = 底层容器 + 标准库算法」这个套路。它根本不是什么神秘数据结构,本质就是「一个连续容器 + `` 里的几个堆函数」——具体说,`push` 等价于 `c.push_back(x)` 然后 `std::push_heap(c.begin(), c.end(), cmp)`;`pop` 等价于 `std::pop_heap(c.begin(), c.end(), cmp)` 然后 `c.pop_back()`;`top` 就是返回 `c.front()`。堆算法维护的「堆序」保证 `c.front()` 永远是当前最优先的元素。 + +复杂度全可以从这个实现推出来。`top()` 直接读首元素,O(1)。`push()` 末尾追加是常数,`push_heap` 把新元素往上浮,最多爬树高 `log n` 层,所以是 O(log n)。`pop()` 里 `pop_heap` 先把首元素和末尾交换、再把新的首元素往下沉,同样最多 `log n` 层,加上一次 `pop_back`,整体 O(log n)。这也解释了为什么 `priority_queue` 的底层**必须是随机访问迭代器**的容器——堆的下沉上浮要在数组里按下标跳着访问(父节点 `i`、孩子 `2i+1`/`2i+2`),链表做不了这种 O(1) 定位,所以底层只能选 `vector` 或 `deque`,默认是 `vector`(连续内存,cache 友好,堆操作更快)。 + +默认比较器是 `std::less`,结果是个**最大堆**——`top()` 返回的是当前最大值。想要最小堆,把比较器换成 `std::greater` 就行。这个「换比较器改变堆方向」的特性,是 priority_queue 最常用的玩法。 + +## 跑跑看:默认最大堆,换个比较器变最小堆 + +光说「默认最大堆」不够实在,我们跑一下看 `top` 到底是谁。 + +```cpp +#include +#include +#include +#include + +int main() +{ + // 默认:vector + less = 最大堆,top() 返回最大值 + std::priority_queue pq; + for (int x : {5, 1, 9, 3, 7}) { + pq.push(x); + } + std::printf("默认(最大堆)依次 pop: "); + while (!pq.empty()) { + std::printf("%d ", pq.top()); + pq.pop(); + } + std::printf("\n"); + + // 换 greater = 最小堆,top() 返回最小值 + std::priority_queue, std::greater> min_pq; + for (int x : {5, 1, 9, 3, 7}) { + min_pq.push(x); + } + std::printf("greater(最小堆)依次 pop: "); + while (!min_pq.empty()) { + std::printf("%d ", min_pq.top()); + min_pq.pop(); + } + std::printf("\n"); + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/pq_demo /tmp/pq_demo.cpp && /tmp/pq_demo +``` + +```text +默认(最大堆)依次 pop: 9 7 5 3 1 +greater(最小堆)依次 pop: 1 3 5 7 9 +``` + +同一个数据集,默认把最大的 9 顶到堆顶,换 `greater` 后最小的 1 顶上来。注意 pop 出来的顺序是**有序的**——这其实就是堆排序的过程,priority_queue 每次 pop 吐出当前最值,连续 pop 到空就得到一个有序序列。也正因为底层就是堆,priority_queue 经常被当成「在线堆排序」用:边 push 边能随时拿到当前最值,`top()` O(1)、增删 O(log n),是很多算法(Dijkstra、合并 k 个有序序列、Top-K)的主力结构。 + +## C++23 的小升级:push_range,一次压一整段 + +C++23 给三个适配器都加了 `push_range`,可以一次压入一个范围。对 `stack`/`queue` 它就是循环 `push` 的语法糖,但对 `priority_queue` 它有实打实的复杂度优势,值得单独说。 + +原因是 priority_queue 维护堆序是有代价的。如果你拿一个 N 元素的范围,循环 `push` N 次,每次 `push_heap` 是 O(log n),总共是 O(n log n);而 `push_range` 的做法是先把整个范围一次性追加到底层容器(`append_range`,O(n)),再对整体做一次 `make_heap`(也是 O(n)),总共只有 O(n)。元素量大的时候,这个差距很明显。 + +```cpp +#include +#include + +int main() +{ + std::vector data{5, 1, 9, 3, 7, 2, 8, 4, 6, 0}; + std::priority_queue pq; + +#if __cplusplus >= 202302L + pq.push_range(data); // C++23:整体 append_range + make_heap,O(n) +#else + for (int x : data) { // C++20 退路:循环 push,O(n log n) + pq.push(x); + } +#endif + return 0; +} +``` + +需要 C++23 的库支持(较新的 libstdc++/libc++),编译时 `-std=c++23`。老环境退回到循环 push 即可,行为一致,只是量大时慢一些。 + +## 挑底层容器的门道 + +绝大多数时候用默认值就好——`stack`/`queue` 用 `deque`、`priority_queue` 用 `vector`,都是委员会挑过的最优默认。要换,通常是为了两个目的之一。一个是 `priority_queue` 想避免默认的 `vector` 扩容拷贝,可以预留给底层 vector——但适配器没直接暴露 `reserve`,得自己先构造好底层容器再 move 进去(`std::priority_queue pq{less{}, my_reserved_vector}`)。另一个是元素类型对 `vector` 不友好(比如非常大、移动昂贵),那 `priority_queue` 可以换 `deque` 当底层。`stack`/`queue` 换底层的场景更少,除非你明确要省内存(用 `list` 避免预分配),否则 `deque` 默认就挺好。 + +```cpp +// 给 priority_queue 预留容量:先 reserve 底层 vector,再 move 进去 +std::vector buf; +buf.reserve(10'000); +std::priority_queue pq{std::less{}, std::move(buf)}; +``` + +## 临了收几句 + +容器适配器的核心就一句:**底层容器 + 受限接口,受限换语义保证**。`stack`/`queue` 是把容器的一端或两端暴露成栈/队列;`priority_queue` 更进一步,用 `` 的堆函数把连续容器包成优先队列——`top` O(1)、增删 O(log n)、默认最大堆、换比较器变最小堆。两个使用上的坎要记牢:一是 `top()` 只是看、要真正取出元素得紧跟 `pop()`;二是 `priority_queue` 没有「删任意元素」「按值查找」的接口,如果你需要这些(比如要中途撤销某个元素),那该用的是 `set` 或 `multiset`,不是 priority_queue。下一篇我们把目光从经典容器移开,看看 C++23/26 给容器家族加的新成员——`flat_map`、`inplace_vector`、`mdspan`。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::stack — cppreference](https://en.cppreference.com/w/cpp/container/stack) +- [std::queue — cppreference](https://en.cppreference.com/w/cpp/container/queue) +- [std::priority_queue — cppreference](https://en.cppreference.com/w/cpp/container/priority_queue) +- [std::priority_queue::push_range(C++23)— cppreference](https://en.cppreference.com/w/cpp/container/priority_queue/push_range) +- [std::push_heap / std::make_heap(堆算法)— cppreference](https://en.cppreference.com/w/cpp/algorithm/push_heap) diff --git a/documents/vol3-standard-library/10-new-containers-cpp23-26.md b/documents/vol3-standard-library/10-new-containers-cpp23-26.md new file mode 100644 index 000000000..b4999154b --- /dev/null +++ b/documents/vol3-standard-library/10-new-containers-cpp23-26.md @@ -0,0 +1,167 @@ +--- +chapter: 7 +cpp_standard: +- 23 +- 26 +description: 梳理 C++23/26 给容器家族补的新成员:flat_map 把红黑树拍平成排序 vector(有序 + cache 友好但插删 O(n))、inplace_vector + 定容不堆分配(C++26)、mdspan 多维视图(C++23,submdspan 切片在 C++26),以及还在路上的 hive 提案 +difficulty: intermediate +order: 10 +platform: host +prerequisites: +- map 与 set 深入 +- unordered_map 与 set 深入 +- span:非拥有的连续视图 +- array:编译期固定大小的聚合容器 +reading_time_minutes: 10 +related: +- 容器选择指南:按操作、内存与失效规则挑对容器 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: 新标准容器:flat_map、inplace_vector 与 mdspan +--- +# 新标准容器:flat_map、inplace_vector 与 mdspan + +## 这一篇讲什么:C++23/26 补的几个长期缺口 + +标准库的 `container` 家族从 C++98 定下来后稳定了二十多年,`vector`/`map`/`unordered_map` 这一套几乎没动过。但实战里有几个一直被人念叨的缺口:有序的关联容器能不能别用红黑树、改用连续存储换 cache 友好?定长的 `array` 和会堆分配的 `vector` 之间,能不能有个「容量上限已知、运行期可变长、又绝不碰堆」的中间态?多维数据(矩阵、图像、体素)能不能有个像 `span` 一样的非拥有多维视图?C++23 和 C++26 这两波正好把这几样补上了——这一篇讲 `flat_map`/`flat_set`、`inplace_vector`、`mdspan` 三个已经标准化的,顺带提一句还在路上的 `hive`。 + +需要先打个预防针:这些组件都很新,`flat_map` 和 `mdspan` 是 C++23(要较新的 libstdc++/libc++),`inplace_vector` 是 C++26,工具链跟不上的话编不过。理解它们的设计思路比马上能用更重要——等你升上 C++23/26 工具链,这些就是现成的弹药。本文所有例子都在 GCC 16.1.1(libstdc++,`-std=c++23` / `-std=c++26`)上实跑通过:`` 和 `` 从 GCC 15 起就有,`` 要 GCC 16。 + +## flat_map / flat_set:把红黑树拍平成排序 vector(C++23) + +先看 `std::flat_map` 和 `std::flat_set`(连同 `flat_multimap`/`flat_multiset`,一共四个)。它们的动机很直接:[map 与 set 深入](06-map-set-deep-dive.md) 讲过 `map`/`set` 底层是红黑树,每个元素一个堆节点,节点之间靠指针串,查找遍历都在节点间跳,cache 命中率差——虽然复杂度是 O(log n),但常数因子被 cache 不友好吃掉一大块。`flat_map` 的做法是**把整棵树拍平成一个排序的连续容器**(默认底层就是 `std::vector`),键值对在内存里挨着排好,查找用二分(O(log n)),但因为是连续内存,cache 友好,实际常数比红黑树小一截。 + +接口上 `flat_map` 是 `map` 的**近乎 drop-in 的替换**——`insert`/`erase`/`find`/`operator[]`/范围遍历都在,甚至有序遍历也能用,迁移成本低。但代价也很清楚,全来自「底层是连续容器」这个事实。第一,**插入和删除是 O(n)**:要在有序数组中间塞一个元素,得把它后面所有元素往后挪;删一个同样要往前挪。这跟红黑树 O(log n) 的增删形成鲜明对比,所以 flat_map 适合「查找和遍历远多于增删」的场景。第二,**迭代器和引用不稳定**:任何插删都可能像 `vector` 一样触发搬移甚至重分配,让所有迭代器失效——而 `map` 的迭代器是永不失效的。一句话,flat_map 用「增删变贵 + 失效变凶」换「查找遍历更快的常数」,数据量不大、读多写少时这买卖划算。 + +```cpp +#include +#include +#include + +int main() +{ + std::flat_map m; + m.insert({3, "three"}); + m.insert({1, "one"}); + m.insert({2, "two"}); // O(n):维护有序要搬移 + + auto it = m.find(2); // O(log n):二分,连续内存 cache 友好 + std::println("find(2) = {}", it->second); + + m.erase(1); // O(n):删了要往前挪 + // it 在这里已失效——和 vector 一样,别再用 + + for (auto [k, v] : m) { // 有序遍历:1 已删,剩下 2, 3 + std::println("{}: {}", k, v); + } + return 0; +} +``` + +## inplace_vector:定容、不堆分配的变长容器(C++26) + +第二个是 `std::inplace_vector`,C++26 进的标准(提案 P0843)。它填的是 `array` 和 `vector` 中间的缝:`array` 大小编译期定死、不能变;`vector` 能变长但要堆分配(扩容时 new 新块、拷贝、释放旧块)。很多时候你要的是「容量上限编译期知道、运行期 size 可变、但绝不碰堆」——`inplace_vector` 就是干这个的。它的元素**直接存在对象内部**(对象本身占 `sizeof(T) * N` 那块空间,放栈上或静态区),运行期可以在 0 到 N 之间增删,不 new、不扩容、不拷贝搬移。 + +最讨喜的一个性质是:**当 `T` 是 trivially copyable 时,`inplace_vector` 本身也是 trivially copyable**。这意味着它可以整体 `memcpy`、可以放进寄存器、可以安全交给 DMA——这些对嵌入式和系统编程极重要,[array 深入](02-array.md) 讲过的「连续内存 + trivially copyable」红利,inplace_vector 同样吃到,而 `std::vector` 因为持有一个堆指针、不是 trivially copyable,是吃不到的。容量超限时的行为也设计得克制:`push_back` 超过 N 会抛 `std::bad_alloc`(异常关闭时退化为 terminate),而想避免异常可以用 C++26 的 `try_push_back`/`try_emplace_back`,它们超限时不抛、返回一个错误指示,适合 `-fno-exceptions` 环境。 + +```cpp +#include +#include + +int main() +{ + std::inplace_vector v; // 容量上限 4,绝不堆分配 + v.push_back(1); + v.push_back(2); + v.push_back(3); // size 现在 3,还能再塞一个 + std::printf("size = %zu, capacity = %zu\n", v.size(), v.capacity()); + // 再 push 到满:v.push_back(4) 成功;v.push_back(5) 超容量,抛 bad_alloc + // 想避免异常用 try_push_back / try_emplace_back——超限不抛,返回失败指示 + return 0; +} +``` + +```bash +g++ -std=c++26 -O2 -o /tmp/ipv_demo /tmp/ipv_demo.cpp && /tmp/ipv_demo +``` + +```text +size = 3, capacity = 4 +``` + +`inplace_vector` 和 `array` 的边界要拎清:`array` 的 size 恒等于 N,是定长;`inplace_vector` 的容量上限是 N,但 size 在 0 到 N 之间运行期可变。要定长用 array,要「上限已知 + 运行期可变 + 不堆分配」用 inplace_vector。 + +## mdspan:span 的多维版(C++23,切片在 C++26) + +第三个是 `std::mdspan`,C++23 进的标准(提案 P0009)。[span 深入](08-span.md) 讲过 `span` 是一维的连续内存视图,但现实里到处是二维三维数据——矩阵、图像、体素场、张量。过去只能拿一个一维指针手动算下标(`data[i * cols + j]`),既丑又容易把行列搞反。`mdspan` 把这种「一块连续内存 + 一个多维形状」包成一个视图类型,让你用多维下标 `m[i, j]` 直接访问,零拷贝、不持有数据、只描述「这块内存怎么按多维解释」。 + +它的模板参数有四个:元素类型、`Extents`(形状,每个维度多大)、`LayoutPolicy`(怎么把多维下标映射成一维偏移,默认 `layout_right` 即行优先,C/C++ 风格)、`Accessor`(怎么读写元素,默认裸访问)。形状用 `std::extents` 描述,维度大小编译期已知就填常量、运行期才知道就填 `std::dynamic_extent`;嫌麻烦可以直接用 `std::dextents`,表示「Rank 个维度全动态」。访问用 `m[i, j]` 这种**多维方括号下标**(靠 C++23 的多维 `operator[]` 语言特性 P2128),不是老的 `m[i][j]`——后者会让人误以为返回子视图,实际上 mdspan 直接把多维索引算成一维偏移、返回元素引用。这里有个容易踩的坑:注意是方括号 `m[i, j]`、不是函数调用 `m(i, j)`;早期 mdspan 参考实现(Kokkos)确实用 `operator()`,但 C++23 标准化后统一改成了多维 `operator[]`,这也是不少老教程和博客还在写 `m(i, j)` 的原因——照抄会编译不过。 + +```cpp +#include +#include + +int main() +{ + int raw[12] = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + }; + // 把 12 个 int 当成 3 行 4 列的二维视图,行优先 + std::mdspan> m(raw); + + std::printf("m[1,2] = %d\n", m[1, 2]); // 第 1 行第 2 列 = 7 + std::printf("m[2,3] = %d\n", m[2, 3]); // 第 2 行第 3 列 = 12 + + // 维度运行期才知道:用 dextents + std::mdspan> d(raw, 3, 4); + std::printf("d[0,0] = %d, rank = %zu\n", d[0, 0], d.rank()); + return 0; +} +``` + +```bash +g++ -std=c++23 -O2 -o /tmp/mdspan_demo /tmp/mdspan_demo.cpp && /tmp/mdspan_demo +``` + +```text +m[1,2] = 7 +m[2,3] = 12 +d[0,0] = 1, rank = 2 +``` + +值得提一句的坑:**`submdspan`(切片)是 C++26,不是 C++23**。mdspan 在 C++23 落地时,切行、切列、切子块的功能没赶上,挪到了 C++26(P2630)。所以如果你想在 C++23 里取一行,还得自己算偏移;要等 C++26 工具链才能用 `std::submdspan(m, std::full_extent, slice)` 这种零拷贝切片。mdspan 的更大意义在于它是 `std::linalg`(线性代数库)的底座——后面的标准里,矩阵运算 API 都建立在 mdspan 之上。 + +## 还在路上:hive 等提案 + +最后说一个常被提及、但**还没进标准**的:`std::hive`(来自 Matt Bentley 的 `plf::hive`,提案 P0909/P2826)。它是个「节点容器」,设计目标是元素地址稳定(插删不影响其它元素的地址)、擦除快、遍历 cache 友好(用块组织节点而不是纯链表),适合那种「要长期持有指向元素的引用、又要频繁增删」的场景。截至 C++26 它仍是提案,没被采纳——想用现在只能上第三方 `plf::hive` 库。这里提一句是为了说明方向:标准委员会在认真考虑「比 list 更好用的节点容器」,但它还不是 `std::` 的一员,别在文章或简历里写成「C++26 的 hive」。 + +## 临了收几句 + +这一波新容器各填一个坑:`flat_map` 给「想要有序、又想要 cache 友好」的场景(代价是增删 O(n) 和像 vector 一样的失效);`inplace_vector` 给「容量上限已知、运行期变长、绝不堆分配」的中间态(C++26,trivially copyable 的特性对嵌入式很香);`mdspan` 给多维数据一个零拷贝的视图类型(C++23,切片 submdspan 要等 C++26)。三个都依赖较新的工具链,flat_map 要 C++23 库支持、inplace_vector 要 C++26,落地前先确认编译器和标准库版本。容器主线到这里就收尾了——从 `array` 到新标准容器,存数据的家伙事儿讲全了;接下来 vol3 会转向「遍历和操作数据」的迭代器与算法。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::flat_map — cppreference](https://en.cppreference.com/w/cpp/container/flat_map) +- [std::flat_set — cppreference](https://en.cppreference.com/w/cpp/container/flat_set) +- [std::inplace_vector(C++26)— cppreference](https://en.cppreference.com/w/cpp/container/inplace_vector) +- [std::mdspan — cppreference](https://en.cppreference.com/w/cpp/container/mdspan) +- [std::submdspan(C++26,P2630)— cppreference](https://en.cppreference.com/w/cpp/container/mdspan/submdspan) +- [Details of std::mdspan from C++23 — C++ Stories](https://www.cppstories.com/2025/cpp23_mdspan/) +- [plf::hive(提案库参考)— GitHub](https://github.com/mattreecebentley/plf_hive) diff --git a/documents/vol3-standard-library/11-initializer-lists.md b/documents/vol3-standard-library/11-initializer-lists.md new file mode 100644 index 000000000..d917e8c07 --- /dev/null +++ b/documents/vol3-standard-library/11-initializer-lists.md @@ -0,0 +1,149 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +description: 讲透 std::initializer_list:编译器为 {...} 生成的只读视图、浅拷贝与 const 元素、元素无法移动进容器的「移动陷阱」、花括号初始化的重载优先级,以及与容器构造的关系 +difficulty: intermediate +order: 11 +platform: host +reading_time_minutes: 6 +related: +- vector 深入:三指针、扩容与迭代器失效 +- span:非拥有的连续视图 +tags: +- host +- cpp-modern +- intermediate +- 容器 +title: std::initializer_list:花括号背后的轻量序列 +--- +# std::initializer_list:花括号背后的轻量序列 + +## initializer_list 是什么:编译器为 `{...}` 生成的只读视图 + +`std::initializer_list` 是 C++11 给「花括号列表初始化」配的标准库类型。你写 `vector{1, 2, 3}` 或 `f({1, 2, 3})` 时,编译器会在背后构造一个 `std::initializer_list`,代表 `{1, 2, 3}` 这段序列。它本身是个极轻量的对象——大致就是一个指针加一个长度,和 `span` 一样属于「不拥有数据的视图」。 + +```cpp +std::initializer_list il = {1, 2, 3}; // 编译器构造,指向底层 const int[3] +il.size(); // 3 +il.begin(); // 指向首元素 +il.end(); // 尾后 +``` + +关键性质有三条:它**不拥有**元素(元素在编译器生成的一段底层 const 数组里),元素是 **const**(只读),拷贝它是**浅拷贝**(就拷贝那个指针和长度,不拷贝元素)。这三条决定了它的全部行为,也埋着它最出名的坑。 + +## 它有多轻:浅拷贝,元素只读 + +initializer_list 的拷贝是浅的——拷贝一个 initializer_list 就是拷贝它内部那个指针(和长度),底层那段 const 数组纹丝不动。所以拿 initializer_list 传参几乎零成本,和传指针差不多。 + +```cpp +void f(std::initializer_list il); // 按值传,其实是浅拷贝(指针 + size) +f({1, 2, 3, 4, 5}); // 不拷贝 5 个 int,只传一个视图 +``` + +但「元素是 const」这一点要记住:initializer_list 里的元素是 `const T`,你拿不到非 const 访问。这看起来无害,却在和移动语义结合时挖了个大坑——下一节专门说。 + +## 移动陷阱:`{...}` 里的元素,进容器时只能拷贝 + +这是 initializer_list 最经典的坑。你想把几个对象塞进 vector,顺手写了 `vector{a, b, c}`,以为现代 C++ 会高效地移动它们——结果它们是**拷贝**进去的。 + +根因就在「元素是 const」:initializer_list 的元素是 `const T`,而移动构造需要 `T&&`(非 const)。vector 从 initializer_list 构造时,得把每个 const 元素拷进自己的存储,const 拷不出 move,只能 copy。哪怕你在花括号里写 `std::move`,也只能让对象「移动进 initializer_list」(因为构造那一步接的是右值),可一旦进了 initializer_list 它就成了 const,再往 vector 里搬就只能拷贝了。 + +咱们量一下,看看到底拷了几次。用一个能统计拷贝 / 移动次数的类型: + +```cpp +#include +#include +#include +#include + +struct Counted { + std::string s; + inline static int copies = 0; + inline static int moves = 0; + Counted(std::string x) : s(std::move(x)) {} + Counted(const Counted& o) : s(o.s) { ++copies; } + Counted(Counted&& o) noexcept : s(std::move(o.s)) { ++moves; } +}; + +int main() +{ + // 场景 1:左值构造 initializer_list → vector + { + Counted a{"a"}, b{"b"}, c{"c"}; + Counted::copies = 0; + Counted::moves = 0; + std::vector v{a, b, c}; + std::cout << "vector{a,b,c} : copies=" << Counted::copies + << " moves=" << Counted::moves << "\n"; + } + // 场景 2:move 进 initializer_list → vector(陷阱:进 vector 那步还是拷贝) + { + Counted a{"a"}, b{"b"}, c{"c"}; + Counted::copies = 0; + Counted::moves = 0; + std::vector v{std::move(a), std::move(b), std::move(c)}; + std::cout << "vector{move(a),...} : copies=" << Counted::copies + << " moves=" << Counted::moves << "\n"; + } + // 场景 3:不用 initializer_list,push_back(move) → 全移动 + { + Counted a{"a"}, b{"b"}, c{"c"}; + Counted::copies = 0; + Counted::moves = 0; + std::vector v; + v.reserve(3); + v.push_back(std::move(a)); + v.push_back(std::move(b)); + v.push_back(std::move(c)); + std::cout << "push_back(move) : copies=" << Counted::copies + << " moves=" << Counted::moves << "\n"; + } + return 0; +} +``` + +```bash +g++ -std=c++17 -O2 -o /tmp/init_list_test /tmp/init_list_test.cpp && /tmp/init_list_test +``` + +```text +vector{a,b,c} : copies=6 moves=0 +vector{move(a),...} : copies=3 moves=3 +push_back(move) : copies=0 moves=3 +``` + +三个场景对比着看。第一种 `vector{a, b, c}`(左值):6 次拷贝、0 次移动——3 次拷贝构造 initializer_list 的元素,再 3 次拷贝进 vector。第二种 `vector{std::move(a), ...}`:3 次拷贝、3 次移动——`std::move` 让对象移动进了 initializer_list(省了 3 次拷贝),但进 vector 那一步还是 3 次拷贝,const 移不动。第三种 `push_back(std::move(...))`:0 次拷贝、3 次移动——绕开 initializer_list,直接 move 进 vector,零拷贝。 + +所以记住这个性能坑:**把若干对象塞进容器,`vector{move(a), ...}` 仍会拷贝进 vector,只有 `push_back(move)` 才零拷贝**。当 T 是重型类型(大 string、大 vector),这个差距是实打实的拷贝开销。 + +## 花括号优先:为什么 `{...}` 总爱匹配 initializer_list 构造 + +initializer_list 还有个「重载偏好」:只要一个类的构造函数有 `initializer_list` 版本,花括号初始化就会优先选它,哪怕别的构造函数看起来更「合身」。最经典的翻车现场是 `vector`: + +```cpp +std::vector v1(10, 0); // 圆括号:10 个 0(count + value 构造) +std::vector v2{10, 0}; // 花括号:两个元素 10 和 0(initializer_list 构造!) +``` + +`v1` 是 10 个 0,`v2` 是 `{10, 0}` 两个元素——同一段意图,圆括号和花括号给出了完全不同的结果,就因为花括号优先匹配了 `initializer_list` 构造。这不是 bug,是规则:花括号初始化在有 `initializer_list` 构造时优先它。所以构造容器时,`(count, value)` 和 `{a, b}` 别混用,意图不同就用不同括号。 + +## 临了收几句 + +`std::initializer_list` 是花括号列表初始化背后的轻量视图:不拥有、元素 const、拷贝浅。它让 `{1, 2, 3}` 这种写法优雅地传给函数和容器,但「元素 const」埋了两个要记的点——一是移动陷阱(`vector{...}` 进容器必拷贝,重型类型要用 `push_back(move)`),二是花括号优先(有 `initializer_list` 构造时,`{}` 会抢着匹配)。下一篇我们离开初始化,去看类型本身的内存布局:对象大小与平凡类型。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::initializer_list — cppreference](https://en.cppreference.com/w/cpp/utility/initializer_list) +- [列表初始化 — cppreference](https://en.cppreference.com/w/cpp/language/list_initialization) diff --git a/documents/vol3-standard-library/12-object-size-and-trivial-types.md b/documents/vol3-standard-library/12-object-size-and-trivial-types.md new file mode 100644 index 000000000..33de26e16 --- /dev/null +++ b/documents/vol3-standard-library/12-object-size-and-trivial-types.md @@ -0,0 +1,224 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 讲透 sizeof/alignof 与内存填充、trivial/trivially_copyable/standard-layout 的精确区分、POD + 的拆分、何时能安全 memcpy、聚合初始化与 C++20 指定初始化 +difficulty: intermediate +order: 12 +platform: host +reading_time_minutes: 8 +related: +- array:编译期固定大小的聚合容器 +tags: +- host +- cpp-modern +- intermediate +- 类型安全 +- 容器 +title: 对象大小、对齐与平凡类型 +--- +# 对象大小、对齐与平凡类型 + +写底层代码、和 C 接口打交道、或优化内存占用时,常被一串看似晦涩的名词绕晕:`sizeof`、`alignof`、`alignas`、`trivial`、`standard-layout`、`trivially_copyable`、聚合(aggregate)……这些概念看起来零碎,其实是一张互相勾连的地图:它们决定对象的内存表示、拷贝语义、能否安全 `memcpy`、能否与 C 结构体 ABI 兼容、以及初始化的灵活性。这一篇把它们理顺。 + +## 大小与对齐:为什么 sizeof 不总是成员之和 + +`sizeof(T)` 报的是对象在内存中**占据的字节数**(完整对象表示,含必要的填充),`alignof(T)` 报的是该类型的**对齐约束**——对象起始地址必须是 `alignof(T)` 的整数倍。为了让每个成员都落在自己要求的对齐上,成员之间、以及结构尾部,可能需要填充(padding)。 + +看一个最常见的例子: + +```cpp +struct A { + char c; // 1 字节,offset 0 + int i; // 4 字节,对齐 4,offset 4 +}; +// offset 0: c,offset 1..3: 填充,offset 4..7: i +// sizeof(A) == 8 +``` + +如果把顺序换一下,填充会变多: + +```cpp +struct B { + char a; // offset 0 + int i; // offset 4(前面填 3 字节) + char b; // offset 8 +}; +// 尾部还要填 3 字节,让 sizeof 是 alignof(B)=4 的倍数 +// sizeof(B) == 12 +``` + +把两个 `char` 放在一起,填充就省下来了: + +```cpp +struct C { + char a; // offset 0 + char b; // offset 1 + int i; // offset 4(前面填 2 字节) +}; +// sizeof(C) == 8 +``` + +同样的成员,只是换了声明顺序,`B` 占 12 字节、`C` 只占 8 字节——这就是「合理安排成员顺序省内存」的来源。结构的整体对齐是它成员中**最大对齐**的值,编译器还会在尾部加填充,保证 `sizeof(T)` 是 `alignof(T)` 的倍数(这关系到数组里元素的间隔)。 + +可以用 `alignas` 强制改变对齐,比如给 SIMD 缓冲区指定 16 字节对齐: + +```cpp +struct alignas(16) Vec4 { + float x, y, z, w; // sizeof == 16,alignof == 16 +}; +``` + +`alignas` 要小心:提高对齐会改变 `sizeof` 和 ABI,在要求对齐访问的硬件上把对象放到不对齐的地址可能直接崩溃。 + +## trivial / trivially_copyable / standard-layout:三个容易混的概念 + +C++ 标准把一组「类型属性」拆开来,精确表达「这个类型的对象在内存里怎么行为」。这是 C++11 的设计(把历史上的 POD 拆成几件事)。先把几个常被混淆的词摆清楚: + +- **trivial(平凡)类型**:特殊成员(默认构造、拷贝/移动构造、赋值、析构)都是编译器生成的、没有自定义逻辑。换句话说,构造/拷贝/析构不产生任何运行时代码——对象的比特位就是它的全部,没有隐藏动作。 +- **trivially_copyable(可平凡拷贝)类型**:可以安全地用 `memcpy` 按字节拷贝(拷完目标有同样的对象表示,且能正常析构)。**这是能否用 `memcpy` 的判据**。 +- **standard-layout(标准布局)类型**:有可预测的内存布局规则(成员按声明顺序排布、没有复杂的访问控制 / 虚继承 / 多重基类导致的不确定布局)。**这是能否和 C struct 布局兼容的判据**。 + +一个关键事实:老概念 `POD`(Plain Old Data)在 C++11 被拆成了 `trivial` 和 `standard-layout`,`POD` 在语义上就是「既 trivial 又 standard-layout」。所以那些和 ABI、C 互操作相关的安全假设,现在用 `std::is_standard_layout_v` 和 `std::is_trivially_copyable_v` 分别检查。 + +举个把它们串起来的例子: + +```cpp +struct S { + int x; + double y; + // 没有用户定义构造/析构/拷贝、没有虚函数、没有基类 +}; +// S 通常是 trivial、trivially_copyable、standard-layout -> POD +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_standard_layout_v); +``` + +对比一个非平凡的: + +```cpp +struct T { + T() { /* 自定义构造 */ } + int x; +}; +// T 不是 trivial(用户定义了构造),通常也不是 trivially_copyable +static_assert(!std::is_trivial_v); +``` + +再强调一条易错的:**trivial ≠ trivially_copyable**,前者强调特殊成员(尤其默认构造)的「平凡性」,后者强调按字节复制是否安全。判断能不能 `memcpy`,用 `std::is_trivially_copyable_v`,别用 `is_trivial`。 + +## 跑跑看:布局与类型属性实测 + +光说 `sizeof(B)==12`、`sizeof(C)==8` 太抽象,咱们用 `static_assert` 把这些假设钉进编译期,再跑出来看一眼: + +```cpp +#include +#include +#include + +struct A { char c; int i; }; +struct B { char a; int i; char b; }; +struct C { char a; char b; int i; }; +struct alignas(16) Vec4 { float x, y, z, w; }; +struct S { int x; double y; }; +struct T { T() {} int x; }; + +static_assert(sizeof(A) == 8); +static_assert(sizeof(B) == 12); +static_assert(sizeof(C) == 8); +static_assert(sizeof(Vec4) == 16 && alignof(Vec4) == 16); +static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); +static_assert(!std::is_trivial_v); + +int main() +{ + std::cout << "sizeof(A)=" << sizeof(A) << " sizeof(B)=" << sizeof(B) + << " sizeof(C)=" << sizeof(C) << " sizeof(Vec4)=" << sizeof(Vec4) << '\n'; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/object_size_test /tmp/object_size_test.cpp && /tmp/object_size_test +``` + +```text +sizeof(A)=8 sizeof(B)=12 sizeof(C)=8 sizeof(Vec4)=16 +``` + +`static_assert` 全部成立(编译通过就说明 A=8、B=12、C=8、Vec4=16、S 既平凡可拷贝又标准布局、T 非平凡——这些假设全对)。这也是这类知识的正确用法:**把你对布局/类型的假设,用 `static_assert` 写进代码**,假设一变编译就拦住你,比注释靠谱得多。 + +## 聚合与指定初始化:从花括号到 C++20 + +聚合(aggregate)是一类方便的类型:它允许用花括号直接列出成员来初始化(aggregate initialization),在写数据描述(配置结构、寄存器映射)时极其直观,也天然适合 `constexpr`。直观地说,聚合就是「没有用户自定义构造函数、没有虚函数、非静态成员都是 public、没有基类(或满足标准布局限制)」的类型——编译器可以简单地把初始化值按成员顺序拷进对象表示。 + +```cpp +struct Point { int x, y; }; +Point p1{1, 2}; // 聚合初始化,成员按声明顺序赋值 + +struct Config { int baud; int parity; int stop_bits; }; +constexpr Config default_cfg{115200, 0, 1}; // 还能 constexpr +``` + +C++20 引入了**指定初始化**(designated initializer,C 早就有,C++20 才正式纳入),让聚合初始化更可读、对成员顺序不敏感: + +```cpp +struct S { int a, b, c; }; +S s1{.b = 2, .a = 1, .c = 3}; // 成员顺序无所谓 +S s2{.a = 1}; // 只初始化 a,其余默认/零初始化 +``` + +嵌套结构和数组下标也能指定,初始化复杂布局(寄存器表、协议头)时特别顺手: + +```cpp +struct Header { uint16_t id; uint16_t flags; }; +struct Packet { Header hdr; uint8_t payload[8]; }; + +Packet pkt{ + .hdr = {.id = 0x1234, .flags = 0x1}, + .payload = {[0] = 0xAA, [3] = 0x55} // 只给第 0、3 个元素赋值 +}; +``` + +注意:指定初始化只适用于**聚合类型**,有用户自定义构造函数的类用不了这个语法。 + +## 把它们用起来:类型属性的实战原则 + +把上面这些点串成几条可操作的原则。第一,定义要和 C 交互或走 DMA 的数据结构(寄存器映射、协议头、序列化格式)时,确保它是 **standard-layout**(布局可预期)且最好 **trivially_copyable**(能 memcpy 或把一块内存直接 reinterpret 成它)——避免虚函数、避免私有非静态成员、别写自定义构造/析构/拷贝,并在接口处用 `static_assert` 把这些不变量钉死: + +```cpp +static_assert(std::is_standard_layout_v); +static_assert(std::is_trivially_copyable_v); +``` + +第二,对齐会影响 `sizeof` 和数组布局。硬件或 DMA 要特殊对齐(16 字节 cache line、SIMD)就用 `alignas` 明确指定,并记得它会改变 `sizeof` 和 ABI。 + +第三,初始化优先用花括号和指定初始化,可读、抗成员顺序变动、还常能 constexpr。 + +第四,拷贝语义:**只有 `trivially_copyable` 的类型,才能安全地 `memcpy(&dst, &src, sizeof(T))`**。对含虚函数、含非平凡析构或特殊成员的类,别做二进制拷贝,老实用构造/拷贝/赋值。 + +## 小结 + +- `alignof` 决定对齐要求,`sizeof` 报真正占用(含填充);合理安排成员顺序能省填充。 +- `trivial`、`trivially_copyable`、`standard-layout` 是标准对类型属性的精细划分:要 `memcpy` 看 `trivially_copyable`,要和 C 布局兼容看 `standard-layout`,`POD` = 既平凡又标准布局。 +- 聚合初始化方便;C++20 指定初始化更可读、不依赖成员顺序。 +- 把对布局和类型的假设用 `static_assert` 写进代码,让编译器替你守这些不变量。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [类型特性(type traits) — cppreference](https://en.cppreference.com/w/cpp/header/type_traits) +- [标准布局类型 — cppreference](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout) +- [指定初始化(C++20) — cppreference](https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers) diff --git a/documents/vol3-standard-library/13-custom-allocators.md b/documents/vol3-standard-library/13-custom-allocators.md new file mode 100644 index 000000000..aaef46587 --- /dev/null +++ b/documents/vol3-standard-library/13-custom-allocators.md @@ -0,0 +1,237 @@ +--- +chapter: 7 +cpp_standard: +- 11 +- 17 +- 20 +description: 讲透自定义分配器:Bump/池/栈三种策略的机制与取舍、placement new 与对象构造析构、C++17 std::pmr 的 memory_resource + 体系(monotonic/pool)与 pmr 容器,以及何时该自己管内存 +difficulty: advanced +order: 13 +platform: host +reading_time_minutes: 7 +related: +- vector 深入:三指针、扩容与迭代器失效 +tags: +- host +- cpp-modern +- advanced +- 内存管理 +- 容器 +title: 自定义分配器与 PMR:自己管内存 +--- +# 自定义分配器与 PMR:自己管内存 + +## 为什么需要自定义分配器 + +默认的 `new` / `malloc` 方便,但有几个软肋:分配时机不确定(可能阻塞实时任务)、产生堆碎片、局部性差、且对所有场景一刀切。当你碰到这些需求时,默认分配器就力不从心了——实时任务不能被偶发 malloc 拖住、启动期想一次性分配避免运行时分配、固定大小小对象高频分配、或想把一大块内存划给某模块便于追踪。这些场景下,自己管内存就成了工程师的基本修行。 + +分配器归根结底两件事:**分配**(给一段未用内存)和**释放**(归还)。在 C++ 里还要管对齐和对象的构造/析构。下面先看三种经典策略,理解机制;再看 C++17 给的标准库答案 `std::pmr`。 + +## 三种经典分配策略 + +### Bump(线性)分配器 + +最简单的分配器:维护一个指针,分配时指针上移,不支持释放单个对象(只能整体 reset)。分配 O(1),适合启动期或短周期任务。 + +```cpp +#include +#include +#include + +class BumpAllocator { + char* start_; + char* ptr_; + char* end_; +public: + BumpAllocator(void* buffer, std::size_t size) + : start_(static_cast(buffer)), + ptr_(start_), + end_(start_ + size) {} + + void* allocate(std::size_t n, std::size_t align = alignof(std::max_align_t)) noexcept + { + std::uintptr_t p = reinterpret_cast(ptr_); + std::size_t mis = p % align; + std::size_t offset = mis ? (align - mis) : 0; + if (n + offset > static_cast(end_ - ptr_)) { + return nullptr; + } + ptr_ += offset; + void* res = ptr_; + ptr_ += n; + return res; + } + + void reset() noexcept { ptr_ = start_; } +}; +``` + +不能释放单个对象(除非加标记/回滚),但实现极简、极快。适合「分配一堆、用完一把 reset」的场景。 + +### 固定大小内存池(Free-list) + +大量相同大小的小对象(消息节点、连接对象),用固定大小池:每个槽固定大小,释放时把槽挂回空闲链表。分配/释放都 O(1),碎片少。 + +```cpp +class SimpleFixedPool { + struct Node { Node* next; }; + void* buffer_; + Node* free_head_; + std::size_t slot_size_; +public: + SimpleFixedPool(void* buf, std::size_t slot_size, std::size_t count) + : buffer_(buf), free_head_(nullptr), + slot_size_(slot_size < sizeof(Node*) ? sizeof(Node*) : slot_size) + { + char* p = static_cast(buffer_); + for (std::size_t i = 0; i < count; ++i) { + Node* n = reinterpret_cast(p + i * slot_size_); + n->next = free_head_; + free_head_ = n; + } + } + void* allocate() noexcept + { + if (!free_head_) return nullptr; + Node* n = free_head_; + free_head_ = n->next; + return n; + } + void deallocate(void* p) noexcept + { + Node* n = static_cast(p); + n->next = free_head_; + free_head_ = n; + } +}; +``` + +`slot_size` 要含对齐和控制信息;要线程安全就得加锁或上 lock-free。 + +### Stack(LIFO)分配器 + +分配/释放呈后进先出时最快,支持「标记 + 回滚到标记」。适合帧分配(每帧分配、帧末统一回收)、短生命周期链。它的 `allocate` 和 Bump 一样(指针上移 + 对齐),多了 mark / rollback: + +```cpp +class StackAllocator { + char* start_; + char* top_; + char* end_; +public: + using Marker = char*; + StackAllocator(void* buf, std::size_t size) + : start_(static_cast(buf)), top_(start_), end_(start_ + size) {} + // allocate 同 Bump(指针上移 + 对齐处理),略 + Marker mark() noexcept { return top_; } + void rollback(Marker m) noexcept { top_ = m; } +}; +``` + +三种策略的取舍:Bump 最简但不支持单释放;Pool 适合固定大小高频;Stack 适合 LIFO 生命周期。它们解决的都是「怎么高效管一块预分配的内存」。 + +## placement new 与对象构造析构 + +分配器只给原始内存(字节),对象的构造/析构是你的事——用 placement new 构造、显式调析构: + +```cpp +#include +#include + +template +T* construct_with(Alloc& a, Args&&... args) +{ + void* mem = a.allocate(sizeof(T), alignof(T)); + if (!mem) return nullptr; + return new (mem) T(std::forward(args)...); +} + +template +void destroy_with(Alloc& a, T* obj) noexcept +{ + if (!obj) return; + obj->~T(); + a.deallocate(static_cast(obj)); +} +``` + +记住:**分配 ≠ 构造**。`allocate` 给内存,`new (mem) T(...)` 才构造;`obj->~T()` 析构,`deallocate` 归还内存。这套「分配 / 构造 / 析构 / 释放」四步,是手写分配器和标准库 allocator 概念的内核。 + +## 标准库的答案:std::pmr(C++17) + +手写分配器能帮你理解机制,但真要在 STL 容器里用「自己的分配策略」,手写一个完整的 `std::allocator` 兼容类型(一堆 typedef、`rebind`)很繁琐。C++17 给了更好的方案:**std::pmr(polymorphic memory resource)**。 + +pmr 的核心是 `std::pmr::memory_resource`——一个抽象基类,提供 `allocate` / `deallocate` 接口(你继承它实现自己的策略)。标准库自带几种现成实现: + +- `monotonic_buffer_resource`:就是前面的 Bump 分配器,在栈 / 静态 buffer 上线性分配,极快、不释放单个、适合帧分配或一次性任务。 +- `synchronized_pool_resource` / `unsynchronized_pool_resource`:固定大小池,适合大量同大小小对象(多线程用 synchronized 版)。 +- `null_memory_resource`:只借不还,用于「此后禁止分配」的场景。 + +然后是 **pmr 容器**:`std::pmr::vector`、`std::pmr::string`、`std::pmr::map` 等,内部用 `polymorphic_allocator`,构造时传一个 `memory_resource*`。换分配策略不用换容器类型(都是 `pmr::vector`),只换 resource——这是 pmr 相对手写 allocator 模板的最大优势:**类型擦除,运行时换策略**。 + +```cpp +#include +#include +#include + +std::byte buffer[4096]; +std::pmr::monotonic_buffer_resource mbr(buffer, sizeof(buffer)); +std::pmr::vector v(&mbr); // v 的内存来自 buffer,不走全局堆 +``` + +## 跑跑看:pmr::vector 配 monotonic buffer + +咱们跑一下,确认 pmr::vector 确实从栈上 buffer 分配: + +```cpp +#include +#include +#include +#include + +int main() +{ + // 栈上一块 buffer,用 monotonic_buffer_resource 当分配源 + std::byte buffer[4096]; + std::pmr::monotonic_buffer_resource mbr(buffer, sizeof(buffer)); + + // pmr::vector 从这块 buffer 分配,不走全局堆 + std::pmr::vector v(&mbr); + for (int i = 0; i < 100; ++i) { + v.push_back(i); + } + std::cout << "v.size() = " << v.size() << "\n"; + std::cout << "vector 的内存来自栈上 buffer,零全局堆分配\n"; + return 0; +} +``` + +```bash +g++ -std=c++20 -O2 -o /tmp/pmr_test /tmp/pmr_test.cpp && /tmp/pmr_test +``` + +```text +v.size() = 100 +vector 的内存来自栈上 buffer,零全局堆分配 +``` + +这个 vector 的元素全来自栈上那块 4096 字节 buffer,没有一次全局 `new`。这就是 pmr + monotonic 的典型用法:把一块预分配内存(栈、静态区、或自管的堆块)喂给容器,获得确定的分配行为、零碎片、零全局堆开销。换个 resource(比如 pool)就换策略,容器代码一行不改。 + +## 临了收几句 + +自定义分配器的核心是「自己管一块内存的分配 / 释放」,三种经典策略——Bump(快、不释放单)、Pool(固定大小高频)、Stack(LIFO)——各有适用场景。理解它们之后,真要在 STL 里用,首选 C++17 的 `std::pmr`:`memory_resource` 抽象 + 标准实现(monotonic / pool)+ pmr 容器,运行时换策略、类型不爆炸。手写分配器用来理解机制、或做 pmr 不覆盖的特殊需求;常规场景,pmr 就够了。容器主线到此告一段落,下一篇我们转向标准库的迭代器与算法体系。 + +想直接上手运行看看效果?点开下面的在线示例(能运行、也能看汇编): + + + +## 参考资源 + +- [std::pmr(memory_resource) — cppreference](https://en.cppreference.com/w/cpp/memory/resource) +- [monotonic_buffer_resource — cppreference](https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource) +- [polymorphic_allocator — cppreference](https://en.cppreference.com/w/cpp/memory/polymorphic_allocator) diff --git a/documents/vol3-standard-library/03-char8-t-utf8.md b/documents/vol3-standard-library/30-char8-t-utf8.md similarity index 95% rename from documents/vol3-standard-library/03-char8-t-utf8.md rename to documents/vol3-standard-library/30-char8-t-utf8.md index 798b015ba..cacc2004c 100644 --- a/documents/vol3-standard-library/03-char8-t-utf8.md +++ b/documents/vol3-standard-library/30-char8-t-utf8.md @@ -3,21 +3,20 @@ chapter: 7 cpp_standard: - 20 - 23 -description: "讲透 C++20 char8_t 的引入动机、u8 字面量类型变更的两个坑与迁移写法,以及 C++23 P2513 对数组初始化的放宽" +description: 讲透 C++20 char8_t 的引入动机、u8 字面量类型变更的两个坑与迁移写法,以及 C++23 P2513 对数组初始化的放宽 difficulty: intermediate -order: 3 +order: 30 platform: host prerequisites: -- '卷一:std::string 与字符串字面量基础' -reading_time_minutes: 12 +- 卷一:std::string 与字符串字面量基础 +reading_time_minutes: 6 tags: - host - cpp-modern - intermediate - 类型安全 -title: "char8_t 与 UTF-8 字符串" +title: char8_t 与 UTF-8 字符串 --- - # char8_t 与 UTF-8 字符串 在 C++20 之前,UTF-8 字符串字面量 `u8"..."` 的类型是 `const char[N]`——跟普通字符串在类型上压根没区别。这听着好像无所谓,其实是不少坑的老巢:你没法在类型层面分清"这一串是 UTF-8"还是"这一串是本地执行字符集",编译器也帮不上你挡住那种把 UTF-8 当裸字节乱打印的错。C++20 引入 `char8_t`,就是要把 UTF-8 从 `char` 那片模糊地带里独立出来,给它一个专属类型,让类型系统替咱们把关。这改动来自提案 **P0482R6**「char8_t: A type for UTF-8 characters and strings」,探测支持看 `__cpp_char8_t`(C++20,值 `201811L`)。 @@ -102,7 +101,7 @@ int main() - vector 深入 - string 深入 - char8_t 与 UTF-8 + 容器选择指南 + array:定长数组 + vector 深入 + string 深入 + deque、list 与 forward_list + map 与 set 深入 + unordered_map 与 set 深入 + span:非拥有视图 + 容器适配器:stack/queue/priority_queue + 新标准容器:flat_map/inplace_vector/mdspan + 初始化列表 + 对象大小与平凡类型 + 自定义分配器 -## 待重写文章 - -以下为早期内容,计划在重写后并入正文章节序列。 +## 字符串与文本 - array(待重写) - 初始化列表(待重写) - span(待重写) - 对象大小与平凡类型(待重写) - 自定义分配器(待重写) + char8_t 与 UTF-8 diff --git a/documents/vol4-advanced/01-coroutine-basics.md b/documents/vol4-advanced/01-coroutine-basics.md index d8decc364..124eb9499 100644 --- a/documents/vol4-advanced/01-coroutine-basics.md +++ b/documents/vol4-advanced/01-coroutine-basics.md @@ -1,14 +1,15 @@ --- -title: 理解C++20的革命特性——协程支持1 -description: '' +chapter: 10 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 23 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 10 -order: 8 +title: 理解C++20的革命特性——协程支持1 +description: '' --- # 理解C++20的革命特性——协程支持1 diff --git a/documents/vol4-advanced/02-coroutine-scheduler.md b/documents/vol4-advanced/02-coroutine-scheduler.md index 8bfc23252..f939612f2 100644 --- a/documents/vol4-advanced/02-coroutine-scheduler.md +++ b/documents/vol4-advanced/02-coroutine-scheduler.md @@ -1,14 +1,15 @@ --- -title: 理解C++20的革命特性——协程支持2:编写简单的协程调度器 -description: '' +chapter: 10 +difficulty: intermediate +order: 9 +platform: host +reading_time_minutes: 25 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 10 -order: 9 +title: 理解C++20的革命特性——协程支持2:编写简单的协程调度器 +description: '' --- # 理解C++20的革命特性——协程支持2:编写简单的协程调度器 diff --git a/documents/vol4-advanced/05-spaceship-operator.md b/documents/vol4-advanced/05-spaceship-operator.md index 346a5be4a..40a657a1b 100644 --- a/documents/vol4-advanced/05-spaceship-operator.md +++ b/documents/vol4-advanced/05-spaceship-operator.md @@ -1,21 +1,21 @@ --- -title: "三路比较运算符(C++20 Spaceship Operator)" -description: "C++20三路比较运算符详解:简化自定义类型的比较逻辑" chapter: 11 -order: 5 -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +description: C++20三路比较运算符详解:简化自定义类型的比较逻辑 difficulty: intermediate -reading_time_minutes: 30 -prerequisites: - - "Chapter 11.1: auto与decltype" - - "Chapter 11.2: 结构化绑定" -cpp_standard: [20] +order: 5 platform: host +prerequisites: +- 'Chapter 11.1: auto与decltype' +- 'Chapter 11.2: 结构化绑定' +reading_time_minutes: 23 +tags: +- cpp-modern +- host +- intermediate +title: 三路比较运算符(C++20 Spaceship Operator) --- - # 嵌入式现代C++开发——三路比较运算符 ## 引言 @@ -1251,7 +1251,7 @@ keys.insert({"Network", "IP"}); diff --git a/documents/vol4-advanced/msvc-cpp-modules.md b/documents/vol4-advanced/msvc-cpp-modules.md index 1e62f5ed0..688828367 100644 --- a/documents/vol4-advanced/msvc-cpp-modules.md +++ b/documents/vol4-advanced/msvc-cpp-modules.md @@ -1,14 +1,15 @@ --- -title: 一文读懂 MSVC C++ Modules:原理、动机与工程实践 -description: '' +chapter: 11 +difficulty: intermediate +order: 8 +platform: host +reading_time_minutes: 8 tags: - cpp-modern - host - intermediate -difficulty: intermediate -platform: host -chapter: 11 -order: 8 +title: 一文读懂 MSVC C++ Modules:原理、动机与工程实践 +description: '' --- # 一文读懂 MSVC C++ Modules:原理、动机与工程实践 diff --git a/documents/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md b/documents/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md index bd1da9c85..403827cd5 100644 --- a/documents/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md +++ b/documents/vol4-advanced/vol2-modern-cpp17/06-designated-initializers.md @@ -1,21 +1,21 @@ --- -title: "指定初始化器" -description: "现代C++指定初始化器详解与嵌入式应用" chapter: 11 -order: 6 -tags: - - cpp-modern - - host - - intermediate +cpp_standard: +- 20 +description: 现代C++指定初始化器详解与嵌入式应用 difficulty: intermediate -reading_time_minutes: 30 -prerequisites: - - "Chapter 11.1: auto与decltype" - - "Chapter 11.2: 结构化绑定" -cpp_standard: [20] +order: 6 platform: host +prerequisites: +- 'Chapter 11.1: auto与decltype' +- 'Chapter 11.2: 结构化绑定' +reading_time_minutes: 15 +tags: +- cpp-modern +- host +- intermediate +title: 指定初始化器 --- - # 嵌入式现代C++开发——指定初始化器 ## 引言 diff --git a/documents/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md b/documents/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md index b1d2e5661..cad9563b3 100644 --- a/documents/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md +++ b/documents/vol4-advanced/vol2-modern-cpp17/07-ranges-basics-and-views.md @@ -11,7 +11,7 @@ order: 7 platform: host prerequisites: - 'Chapter 8: 类型安全' -reading_time_minutes: 16 +reading_time_minutes: 11 tags: - cpp-modern - host diff --git a/documents/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md b/documents/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md index a559ec984..741ddd87d 100644 --- a/documents/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md +++ b/documents/vol4-advanced/vol2-modern-cpp17/08-ranges-pipeline-in-practice.md @@ -11,7 +11,7 @@ order: 8 platform: host prerequisites: - 'Chapter 8: 类型安全' -reading_time_minutes: 19 +reading_time_minutes: 12 tags: - cpp-modern - host diff --git a/documents/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md b/documents/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md index 4556f8529..bed77dc72 100644 --- a/documents/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md +++ b/documents/vol5-concurrency/ch00-concurrency-fundamentals/02-concurrency-problems.md @@ -1,25 +1,27 @@ --- -title: "并发基本问题" -description: "识别并发编程中最常见的 bug:data race、race condition、死锁、活锁、饥饿与优先级反转" chapter: 0 -order: 2 -tags: - - host - - cpp-modern - - beginner - - atomic - - mutex +cpp_standard: +- 11 +- 17 +- 20 +description: 识别并发编程中最常见的 bug:data race、race condition、死锁、活锁、饥饿与优先级反转 difficulty: beginner +order: 2 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 17, 20] prerequisites: - - "为什么需要并发" +- 为什么需要并发 +reading_time_minutes: 15 related: - - "mutex 与 RAII 守卫" - - "std::atomic 原子操作" +- mutex 与 RAII 守卫 +- std::atomic 原子操作 +tags: +- host +- cpp-modern +- beginner +- atomic +- mutex +title: 并发基本问题 --- - # 并发基本问题 上一篇我们聊了"为什么需要并发",建立了基本的判断力。但知道为什么还不够,我们还需要知道并发代码到底会出什么问题。说实话,并发 bug 令人头疼的地方不在于它有多复杂——而在于它**不可预测**。一个多线程程序在你本机跑了十万次都正常,上线后凌晨三点在客户环境崩了,你拿到 dump 一看,跟你的预期完全对不上! diff --git a/documents/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md b/documents/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md index d40061395..782fa926d 100644 --- a/documents/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md +++ b/documents/vol5-concurrency/ch00-concurrency-fundamentals/03-cpu-cache-and-os-threads.md @@ -1,26 +1,28 @@ --- -title: "CPU cache 与 OS 线程" -description: "从硬件缓存层次结构到操作系统线程模型,理解多线程程序运行的真实物理舞台" chapter: 0 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - 基础 - - atomic +cpp_standard: +- 11 +- 17 +- 20 +description: 从硬件缓存层次结构到操作系统线程模型,理解多线程程序运行的真实物理舞台 difficulty: intermediate +order: 3 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 17, 20] prerequisites: - - "为什么需要并发" - - "并发基本问题" +- 为什么需要并发 +- 并发基本问题 +reading_time_minutes: 27 related: - - "std::thread 基础" - - "原子操作模式" +- std::thread 基础 +- 原子操作模式 +tags: +- host +- cpp-modern +- intermediate +- 基础 +- atomic +title: CPU cache 与 OS 线程 --- - # CPU cache 与 OS 线程 前两篇我们建立了并发的"为什么"和"出什么问题"这两层认知。但有一个很现实的事情一直被我们有意无意地绕过去了:多线程程序到底跑在什么样的硬件和操作系统之上?我们写 `std::thread t(func)` 的时候,背后发生了什么?为什么有时候多线程程序跑起来不仅没变快,反而比单线程还慢? diff --git a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md index 671a87786..e10364fdd 100644 --- a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md +++ b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/01-std-thread.md @@ -1,24 +1,26 @@ --- -title: "std::thread 基础" -description: "掌握 C++ 线程的创建、join、detach、ID 与硬件并发查询,建立第一个多线程程序的直觉" chapter: 1 -order: 1 -tags: - - host - - cpp-modern - - beginner - - 入门 +cpp_standard: +- 11 +- 14 +- 17 +description: 掌握 C++ 线程的创建、join、detach、ID 与硬件并发查询,建立第一个多线程程序的直觉 difficulty: beginner +order: 1 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17] prerequisites: - - "CPU cache 与 OS 线程" +- CPU cache 与 OS 线程 +reading_time_minutes: 18 related: - - "线程参数与生命周期" - - "线程所有权与 RAII" +- 线程参数与生命周期 +- 线程所有权与 RAII +tags: +- host +- cpp-modern +- beginner +- 入门 +title: std::thread 基础 --- - # std::thread 基础 前一章我们聊了 CPU cache 的层次结构、MESI 协议、false sharing,也看了 Linux 的线程模型和 futex 机制——这些都是多线程程序运行的物理舞台。但光知道舞台长什么样还不够,我们得亲自上台演一演。这一篇就是我们的第一次登台:从 `std::thread` 的构造开始,搞清楚线程怎么创建、怎么等待、怎么"放手不管",以及在操作过程中有哪些一不留神就会踩的坑。 @@ -457,7 +459,7 @@ int main() diff --git a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md index 659840076..2e6d36b94 100644 --- a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md +++ b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/02-thread-arguments-and-lifetime.md @@ -1,24 +1,27 @@ --- -title: "线程参数与生命周期" -description: "深入线程参数的传递机制,识别悬垂引用与对象析构顺序引发的并发 bug" chapter: 1 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - 内存管理 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 深入线程参数的传递机制,识别悬垂引用与对象析构顺序引发的并发 bug difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 22 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "std::thread 基础" +- std::thread 基础 +reading_time_minutes: 18 related: - - "线程所有权与 RAII" - - "CPU cache 与 OS 线程" +- 线程所有权与 RAII +- CPU cache 与 OS 线程 +tags: +- host +- cpp-modern +- intermediate +- 内存管理 +title: 线程参数与生命周期 --- - # 线程参数与生命周期 上一篇我们学会了 `std::thread` 的基本操作——创建、join、detach、获取 ID。当时我们有意无意地绕过了一个非常重要的话题:传给线程的参数,到底是怎么到线程函数手里的?为什么有时候我明明传了一个引用进去,线程里改了外面的变量却没变?为什么用了 `std::ref` 之后,程序有时候又莫名其妙地崩了? diff --git a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md index 00bb7a6f8..dc5a648eb 100644 --- a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md +++ b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/03-thread-ownership-and-raii.md @@ -1,24 +1,27 @@ --- -title: "线程所有权与 RAII" -description: "用 RAII 包装 std::thread,实现异常安全的 joining_thread guard 与作用域退出清理" chapter: 1 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - RAII +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 用 RAII 包装 std::thread,实现异常安全的 joining_thread guard 与作用域退出清理 difficulty: intermediate +order: 3 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程参数与生命周期" +- 线程参数与生命周期 +reading_time_minutes: 18 related: - - "mutex 与 RAII 锁" - - "jthread 与停止令牌" +- mutex 与 RAII 锁 +- jthread 与停止令牌 +tags: +- host +- cpp-modern +- intermediate +- RAII +title: 线程所有权与 RAII --- - # 线程所有权与 RAII 上一篇我们搞清楚了 `std::thread` 的参数传递和生命周期管理,知道了一个 `std::thread` 对象在销毁之前必须被 `join()` 或 `detach()`,否则程序会直接 `std::terminate()`。但说实话,每次手动调 `join()` 是一件很烦的事情——不是因为它难,而是因为它太容易忘了。特别是在有异常抛出的代码路径里,你可能在一个函数中间就跳出去了,后面的 `join()` 根本执行不到。更糟糕的是,如果你的函数有多个 `return` 路径,每一条都得记得 `join()`,漏一个就是定时炸弹。 diff --git a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/04-thread-local-and-call-once.md b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/04-thread-local-and-call-once.md index 2574abfa7..fa083e8d3 100644 --- a/documents/vol5-concurrency/ch01-thread-lifecycle-raii/04-thread-local-and-call-once.md +++ b/documents/vol5-concurrency/ch01-thread-lifecycle-raii/04-thread-local-and-call-once.md @@ -1,23 +1,26 @@ --- -title: "thread_local 与 call_once" -description: "掌握线程局部存储与一次性初始化机制,写出线程安全的懒初始化与全局状态" chapter: 1 -order: 4 -tags: - - host - - cpp-modern - - intermediate - - 内存管理 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握线程局部存储与一次性初始化机制,写出线程安全的懒初始化与全局状态 difficulty: intermediate +order: 4 platform: host -reading_time_minutes: 18 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程所有权与 RAII" +- 线程所有权与 RAII +reading_time_minutes: 19 related: - - "线程参数与生命周期" +- 线程参数与生命周期 +tags: +- host +- cpp-modern +- intermediate +- 内存管理 +title: thread_local 与 call_once --- - # thread_local 与 call_once 上一篇我们用 RAII 解决了线程所有权和生命周期管理的问题。这一篇我们要看另一个维度的问题:当多个线程需要访问某些"全局状态"时,怎么既保证线程安全又不牺牲性能? diff --git a/documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md b/documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md index e56e1e346..7f74c5e72 100644 --- a/documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md +++ b/documents/vol5-concurrency/ch02-mutex-condition-sync/01-mutex-and-raii-guards.md @@ -1,25 +1,28 @@ --- -title: "mutex 与 RAII 锁" -description: "系统梳理 mutex 家族与 RAII 锁守卫,从 lock_guard 到 scoped_lock 的演进与最佳实践" chapter: 2 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - mutex - - RAII守卫 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 系统梳理 mutex 家族与 RAII 锁守卫,从 lock_guard 到 scoped_lock 的演进与最佳实践 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 22 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程所有权与 RAII" +- 线程所有权与 RAII +reading_time_minutes: 17 related: - - "死锁与锁顺序" - - "condition_variable 与等待语义" +- 死锁与锁顺序 +- condition_variable 与等待语义 +tags: +- host +- cpp-modern +- intermediate +- mutex +- RAII守卫 +title: mutex 与 RAII 锁 --- - # mutex 与 RAII 锁 上一篇我们聊了线程所有权与 RAII,掌握了 `std::thread` 的生命周期管理和基于作用域的资源控制思路。现在问题来了:有了线程,线程之间怎么安全地共享数据?我们在并发基本问题那一篇里已经见过 data race 的威力了——两个线程同时写一个 `int`,结果就能从 2000000 跑成 1345687。解决 data race 最通用的手段就是互斥量(mutex),而 C++ 标准库为我们准备了一整个 mutex 家族和配套的 RAII 锁守卫。 @@ -396,7 +399,7 @@ void safe_thread() diff --git a/documents/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md b/documents/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md index aacc89f49..117f4ea6e 100644 --- a/documents/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md +++ b/documents/vol5-concurrency/ch02-mutex-condition-sync/02-deadlock-and-lock-ordering.md @@ -1,23 +1,26 @@ --- -title: "死锁与锁顺序" -description: "深入死锁的四个必要条件,掌握锁顺序约束、try_lock 回退与 scoped_lock 多锁获取策略" chapter: 2 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - mutex +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 深入死锁的四个必要条件,掌握锁顺序约束、try_lock 回退与 scoped_lock 多锁获取策略 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "mutex 与 RAII 锁" +- mutex 与 RAII 锁 +reading_time_minutes: 18 related: - - "condition_variable 与等待语义" +- condition_variable 与等待语义 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: 死锁与锁顺序 --- - # 死锁与锁顺序 上一篇我们系统梳理了 mutex 家族和三个 RAII 锁守卫,掌握了从 `lock_guard` 到 `scoped_lock` 的选择策略。那一篇里我们反复提到"死锁"这个词,但并没有深入展开——因为我们先把工具准备好,再来对付这个真正的敌人。这一篇我们就来正面对抗死锁。 diff --git a/documents/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md b/documents/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md index 4e21cd0bc..66b1bc3b9 100644 --- a/documents/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md +++ b/documents/vol5-concurrency/ch02-mutex-condition-sync/03-condition-variable.md @@ -1,25 +1,28 @@ --- -title: "condition_variable 与等待语义" -description: "掌握条件变量的 wait/notify 机制,理解虚假唤醒、谓词写法与丢失唤醒问题" chapter: 2 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - mutex - - 异步编程 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握条件变量的 wait/notify 机制,理解虚假唤醒、谓词写法与丢失唤醒问题 difficulty: intermediate +order: 3 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "mutex 与 RAII 锁" +- mutex 与 RAII 锁 +reading_time_minutes: 18 related: - - "读写锁与 shared_mutex" - - "线程安全队列" +- 读写锁与 shared_mutex +- 线程安全队列 +tags: +- host +- cpp-modern +- intermediate +- mutex +- 异步编程 +title: condition_variable 与等待语义 --- - # condition_variable 与等待语义 上一篇我们聊了 mutex 和 RAII 锁——知道怎么保护临界区、怎么避免死锁。但有一个问题我们没有解决:如果线程需要"等某个条件成立"才能继续执行,光靠 mutex 怎么做?最朴素的想法是写一个循环,反复加锁检查条件,不满足就解锁睡一小会儿再试——这就是所谓的**忙等待(busy-wait)**或者**轮询(polling)**。它能工作,但代价是白白消耗 CPU 周期,而且"睡多久"这个参数很难调准:睡短了浪费 CPU,睡长了响应迟钝。 diff --git a/documents/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md b/documents/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md index 4235f0bd1..66bf16e20 100644 --- a/documents/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md +++ b/documents/vol5-concurrency/ch02-mutex-condition-sync/04-shared-mutex.md @@ -1,24 +1,25 @@ --- -title: "读写锁与 shared_mutex" -description: "C++17 shared_mutex 的读多写少场景应用,分析写饥饿问题与性能边界" chapter: 2 -order: 4 -tags: - - host - - cpp-modern - - intermediate - - mutex +cpp_standard: +- 17 +- 20 +description: C++17 shared_mutex 的读多写少场景应用,分析写饥饿问题与性能边界 difficulty: intermediate +order: 4 platform: host -reading_time_minutes: 20 -cpp_standard: [17, 20] prerequisites: - - "condition_variable 与等待语义" +- condition_variable 与等待语义 +reading_time_minutes: 14 related: - - "mutex 与 RAII 锁" - - "线程安全容器设计" +- mutex 与 RAII 锁 +- 线程安全容器设计 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: 读写锁与 shared_mutex --- - # 读写锁与 shared_mutex 到目前为止,我们讨论的同步原语都是"排他式"的——一个线程拿到锁,其他所有线程都得在外面等着。但现实中有一大类场景并不是这样的:**读多写少**。配置数据、缓存、路由表、字典——这些东西绝大部分时间在被读取,偶尔才被更新。如果每次读取都要排他地获取 mutex,那多个读线程之间就被不必要地串行化了——它们完全可以并发地读取同一个数据结构,因为读操作不会修改任何状态。 diff --git a/documents/vol5-concurrency/ch02-mutex-condition-sync/05-latch-barrier-semaphore.md b/documents/vol5-concurrency/ch02-mutex-condition-sync/05-latch-barrier-semaphore.md index e0b129c56..97ba99c8d 100644 --- a/documents/vol5-concurrency/ch02-mutex-condition-sync/05-latch-barrier-semaphore.md +++ b/documents/vol5-concurrency/ch02-mutex-condition-sync/05-latch-barrier-semaphore.md @@ -1,24 +1,24 @@ --- -title: "latch、barrier 与 semaphore" -description: "C++20 同步原语:一次性/多次使用的同步屏障与计数信号量,场景选择与工程模式" chapter: 2 -order: 5 -tags: - - host - - cpp-modern - - advanced - - mutex +cpp_standard: +- 20 +description: C++20 同步原语:一次性/多次使用的同步屏障与计数信号量,场景选择与工程模式 difficulty: advanced +order: 5 platform: host -reading_time_minutes: 20 -cpp_standard: [20] prerequisites: - - "condition_variable 与等待语义" +- condition_variable 与等待语义 +reading_time_minutes: 19 related: - - "atomic 操作" - - "线程池设计" +- atomic 操作 +- 线程池设计 +tags: +- host +- cpp-modern +- advanced +- mutex +title: latch、barrier 与 semaphore --- - # latch、barrier 与 semaphore 上一篇我们深入拆解了 `condition_variable` 的等待-通知机制——虚假唤醒、丢失唤醒、带谓词的 `wait`。有了这些基础,我们现在可以面对一个更实际的问题:很多时候我们并不需要"某个条件满足才继续"这种通用的等待语义,而是只需要"等到大家都到齐了再继续"或者"限制同时访问资源的线程数量"。这两种需求分别对应**屏障(barrier)**和**信号量(semaphore)**两种同步模式,而 C++20 终于把这两个概念以 `std::latch`、`std::barrier` 和 `std::counting_semaphore` 的形式纳入了标准库。 diff --git a/documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md b/documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md index 17bbda1b3..03844ae38 100644 --- a/documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md +++ b/documents/vol5-concurrency/ch03-atomic-memory-model/01-atomic-operations.md @@ -1,24 +1,28 @@ --- -title: "atomic 操作" -description: "std::atomic 的完整操作手册:load/store、fetch_add、compare_exchange 与 lock-free 判断" chapter: 3 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - atomic +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: std::atomic 的完整操作手册:load/store、fetch_add、compare_exchange 与 lock-free + 判断 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 22 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "latch、barrier 与 semaphore" +- latch、barrier 与 semaphore +reading_time_minutes: 20 related: - - "内存序详解" - - "原子操作模式" +- 内存序详解 +- 原子操作模式 +tags: +- host +- cpp-modern +- intermediate +- atomic +title: atomic 操作 --- - # atomic 操作 到目前为止,我们讨论的同步原语——mutex、condition variable、latch、barrier、semaphore——本质上都是"先加锁,再操作,最后解锁"的思路。它们很安全,也很直观,但有一个共同的代价:哪怕你只想保护一个简单的整数自增,也得走一遍 lock → modify → unlock 的完整流程。对于"修改一个变量"这种粒度极小的操作来说,这套流程的重量就显得不太匹配了。 @@ -455,7 +459,7 @@ int b = x.load(); ` 的操作集——load、store、fetch_add、compare_exchange,一口气用下来默认参数就能跑。但你有没有注意到,几乎每个原子操作都有一个可选的 `std::memory_order` 参数?很多人(包括当年的笔者)都是直接无视它,反正默认值能工作嘛。 diff --git a/documents/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md b/documents/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md index 9e454824b..00e099bbf 100644 --- a/documents/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md +++ b/documents/vol5-concurrency/ch03-atomic-memory-model/05-atomic-patterns.md @@ -1,25 +1,28 @@ --- -title: "原子操作模式" -description: "SeqLock、Double-Checked Locking、引用计数与发布-订阅等经典原子模式的正确实现" chapter: 3 -order: 5 -tags: - - host - - cpp-modern - - advanced - - atomic - - 无锁 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: SeqLock、Double-Checked Locking、引用计数与发布-订阅等经典原子模式的正确实现 difficulty: advanced +order: 5 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "fence 与编译器屏障" - - "atomic_wait 与 atomic_ref" +- fence 与编译器屏障 +- atomic_wait 与 atomic_ref +reading_time_minutes: 26 related: - - "无锁编程基础" +- 无锁编程基础 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +title: 原子操作模式 --- - # 原子操作模式 > 📖 **应用场景**:这一篇的原子模式在嵌入式里有个高频落地——ISR 和主循环之间无锁共享变量。如果你在写单片机固件,配着 [卷八·中断安全编程](../../vol8-domains/embedded/05-interrupt-safe-coding.md)看会更通透。 diff --git a/documents/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md b/documents/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md index c9521d747..bd141c9ba 100644 --- a/documents/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md +++ b/documents/vol5-concurrency/ch04-concurrent-data-structures/01-thread-safe-queue.md @@ -1,24 +1,27 @@ --- -title: "线程安全队列" -description: "用 mutex + condition_variable 构建可关闭、支持超时的 bounded blocking queue" chapter: 4 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - mutex +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 用 mutex + condition_variable 构建可关闭、支持超时的 bounded blocking queue difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "condition_variable 与等待语义" +- condition_variable 与等待语义 +reading_time_minutes: 26 related: - - "线程安全容器设计" - - "SPSC 与 MPMC 队列" +- 线程安全容器设计 +- SPSC 与 MPMC 队列 +tags: +- host +- cpp-modern +- intermediate +- mutex +title: 线程安全队列 --- - # 线程安全队列 上一篇我们在 condition_variable 那篇里写了一个简化版的 `BoundedQueue`——有 `push` 和 `pop`,能阻塞,能通知。说实话,当时写完觉得还挺像回事的,但如果你直接把它扔进生产代码里,我敢打赌不用两天就会出问题:队列怎么优雅地关闭?消费者阻塞在 `pop` 上的时候,生产者线程崩了怎么办?如果我不想无限期等待,只想尝试取一个元素呢?如果我想从外部取消等待呢? diff --git a/documents/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md b/documents/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md index 243b289e0..6385e50b7 100644 --- a/documents/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md +++ b/documents/vol5-concurrency/ch04-concurrent-data-structures/02-thread-safe-containers.md @@ -1,25 +1,28 @@ --- -title: "线程安全容器设计" -description: "粗粒度锁、细粒度锁、分片锁与 copy-on-write 四种策略的设计与权衡" chapter: 4 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - mutex - - 容器 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 粗粒度锁、细粒度锁、分片锁与 copy-on-write 四种策略的设计与权衡 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程安全队列" - - "读写锁与 shared_mutex" +- 线程安全队列 +- 读写锁与 shared_mutex +reading_time_minutes: 23 related: - - "无锁编程基础" +- 无锁编程基础 +tags: +- host +- cpp-modern +- intermediate +- mutex +- 容器 +title: 线程安全容器设计 --- - # 线程安全容器设计 说实话,笔者第一次需要写一个"多线程能用的 map"的时候,第一反应是——这有什么难的,不就是在每个操作外面加个 lock_guard 吗?然后真的动手写了才发现,事情远没有那么简单。加锁本身不难,难的是加对了、加够了、加得恰到好处。锁加粗了性能炸,锁加细了正确性炸,锁加错位置了直接 data race 炸。 diff --git a/documents/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md b/documents/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md index 8dee6bf03..8f421965b 100644 --- a/documents/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md +++ b/documents/vol5-concurrency/ch04-concurrent-data-structures/03-lock-free-basics.md @@ -1,24 +1,27 @@ --- -title: "无锁编程基础" -description: "CAS 循环、lock-free vs wait-free、ABA 问题和内存回收挑战,建立无锁编程的基本判断力" chapter: 4 -order: 3 -tags: - - host - - cpp-modern - - advanced - - atomic - - 无锁 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: CAS 循环、lock-free vs wait-free、ABA 问题和内存回收挑战,建立无锁编程的基本判断力 difficulty: advanced +order: 3 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "原子操作模式" +- 原子操作模式 +reading_time_minutes: 28 related: - - "SPSC 与 MPMC 队列" +- SPSC 与 MPMC 队列 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +title: 无锁编程基础 --- - # 无锁编程基础 前两篇我们用 mutex + condition_variable 构建了线程安全队列和容器,在 ch03 我们把 `std::atomic` 的操作集和六种内存序全部拆完了,还在"原子操作模式"那篇里写了 SeqLock、自旋锁和引用计数。那些内容回答的是"怎么做原子操作"的问题,但还没有碰过一个更深层的问题:**如果我们完全不用锁,能不能写出正确的并发数据结构?** diff --git a/documents/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md b/documents/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md index aaf493827..bd8ef2118 100644 --- a/documents/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md +++ b/documents/vol5-concurrency/ch04-concurrent-data-structures/04-lock-free-queues.md @@ -1,26 +1,29 @@ --- -title: "SPSC 与 MPMC 队列" -description: "从 ring buffer SPSC 到 Michael-Scott MPMC 队列,缓存友好的生产者-消费者队列设计" chapter: 4 -order: 4 -tags: - - host - - cpp-modern - - advanced - - atomic - - 无锁 - - 循环缓冲区 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从 ring buffer SPSC 到 Michael-Scott MPMC 队列,缓存友好的生产者-消费者队列设计 difficulty: advanced +order: 4 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "无锁编程基础" +- 无锁编程基础 +reading_time_minutes: 26 related: - - "线程安全队列" - - "线程池设计" +- 线程安全队列 +- 线程池设计 +tags: +- host +- cpp-modern +- advanced +- atomic +- 无锁 +- 循环缓冲区 +title: SPSC 与 MPMC 队列 --- - # SPSC 与 MPMC 队列 说实话,笔者写这篇的时候反复纠结了很久——到底要不要手把手带大家实现一遍 Michael-Scott 队列?这东西的 CAS 逻辑看着不复杂,但你一旦动手写就会发现到处是坑,尤其是那个 `dequeue` 里的数据读取和 CAS 的时序问题,笔者自己第一次实现的时候就翻车了。不过纠结归纠结,这条路还是得走一遍,因为只有自己写过了才能真正理解"为什么 SPSC 比 MPMC 快那么多"。 diff --git a/documents/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md b/documents/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md index fa9b9d132..180771a4f 100644 --- a/documents/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md +++ b/documents/vol5-concurrency/ch05-future-task-threadpool/01-std-async-and-future.md @@ -1,24 +1,27 @@ --- -title: "std::async 与 future" -description: "理解 std::async 的 launch policy、future.get 的阻塞语义与 deferred 陷阱" chapter: 5 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 异步编程 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 理解 std::async 的 launch policy、future.get 的阻塞语义与 deferred 陷阱 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程安全队列" +- 线程安全队列 +reading_time_minutes: 24 related: - - "promise 与 packaged_task" - - "线程池设计" +- promise 与 packaged_task +- 线程池设计 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +title: std::async 与 future --- - # std::async 与 future 写到这一篇,说实话笔者是松了一口气的。前面几章我们一直在跟 `std::thread`、`std::mutex`、`std::atomic` 这些底层原语打交道,直接操控线程的创建、同步、甚至内存序。这玩意儿写多了确实累——你得自己管线程生命周期,自己设计同步机制,自己把结果从子线程搬回主线程,还得操心异常怎么传回来、线程崩了怎么办。每次写一个并发任务都要重复这套流程,写着写着你就会想:有没有一种方式,让我只管说"帮我异步跑一个任务,把结果拿回来",其他的你别烦我? diff --git a/documents/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md b/documents/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md index 36a2746a8..da2d9f053 100644 --- a/documents/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md +++ b/documents/vol5-concurrency/ch05-future-task-threadpool/02-promise-and-packaged-task.md @@ -1,24 +1,27 @@ --- -title: "promise 与 packaged_task" -description: "手动设置 future 的值与异常,用 packaged_task 封装可调用对象,构建灵活的任务通道" chapter: 5 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - 异步编程 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 手动设置 future 的值与异常,用 packaged_task 封装可调用对象,构建灵活的任务通道 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 20 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "std::async 与 future" +- std::async 与 future +reading_time_minutes: 23 related: - - "jthread 与停止令牌" - - "线程池设计" +- jthread 与停止令牌 +- 线程池设计 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +title: promise 与 packaged_task --- - # promise 与 packaged_task 上一篇我们用 `std::async` 启动异步任务,通过 `std::future` 拿回结果。整个过程确实方便,但笔者折腾下来发现有一个限制让人不太舒服:`std::async` 把"启动任务"和"获取结果"绑死了——你调用了 `std::async`,任务就启动了,返回的 future 就跟这个任务关联了。你没法先创建一个 future,然后在某个时刻往里面塞值;也没法把一个已有的函数对象包装成异步任务,塞到队列里稍后再执行。一旦你想做"任务提交"和"任务执行"分离的事情(比如线程池),`std::async` 就不够用了。 diff --git a/documents/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md b/documents/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md index 22570c659..bc32c2d17 100644 --- a/documents/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md +++ b/documents/vol5-concurrency/ch05-future-task-threadpool/03-jthread-and-stop-token.md @@ -1,26 +1,26 @@ --- -title: "jthread 与停止令牌" -description: "C++20 的自动 join 线程与协作式取消机制:stop_source、stop_token、stop_callback 的完整用法" chapter: 5 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - 异步编程 - - RAII守卫 - - 进阶 +cpp_standard: +- 20 +description: C++20 的自动 join 线程与协作式取消机制:stop_source、stop_token、stop_callback 的完整用法 difficulty: intermediate +order: 3 platform: host -reading_time_minutes: 22 -cpp_standard: [20] prerequisites: - - "promise 与 packaged_task" +- promise 与 packaged_task +reading_time_minutes: 18 related: - - "线程所有权与 RAII" - - "线程池设计" +- 线程所有权与 RAII +- 线程池设计 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +- RAII守卫 +- 进阶 +title: jthread 与停止令牌 --- - # jthread 与停止令牌 说实话,笔者在写前面几篇的时候,用 `std::thread` 用得挺心虚的。每次都要手动 `join()`,稍不留神就 `std::terminate()` 收场,想中途停掉一个线程还得自己搞 `std::atomic` 的标志位——这都 2026 年了,C++ 的线程管理居然还这么"原始"。上一篇我们用 `std::promise` 和 `std::packaged_task` 构建了手动控制异步任务的能力,但底层的线程工具一直没升级,所以这一篇我们就要把这块短板补上。 diff --git a/documents/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md b/documents/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md index 060d1e17a..534e56b8a 100644 --- a/documents/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md +++ b/documents/vol5-concurrency/ch05-future-task-threadpool/04-thread-pool.md @@ -1,26 +1,29 @@ --- -title: "线程池设计" -description: "从 worker + 任务队列 + condition_variable 出发,构建支持 future 返回、异常传播和优雅关闭的线程池" chapter: 5 -order: 4 -tags: - - host - - cpp-modern - - advanced - - 异步编程 - - mutex +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 从 worker + 任务队列 + condition_variable 出发,构建支持 future 返回、异常传播和优雅关闭的线程池 difficulty: advanced +order: 4 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "jthread 与停止令牌" - - "promise 与 packaged_task" +- jthread 与停止令牌 +- promise 与 packaged_task +reading_time_minutes: 34 related: - - "线程安全队列" - - "std::async 与 future" +- 线程安全队列 +- std::async 与 future +tags: +- host +- cpp-modern +- advanced +- 异步编程 +- mutex +title: 线程池设计 --- - # 线程池设计 前面几篇我们把 `std::async`、`std::future`、`std::promise`、`std::packaged_task` 这套异步基础设施逐个拆解了一遍,也在 packaged_task 那篇的末尾搭了一个单线程的 `SimpleTaskQueue` 作为引子。那个简陋的队列虽然跑得通,但它只有一个 worker 线程——说实话,提交 4 个任务只能排着队一个一个跑,完全没有并行可言,跟直接在主线程调用也没什么本质区别。 diff --git a/documents/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md b/documents/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md index ece133d7d..42206e556 100644 --- a/documents/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md +++ b/documents/vol5-concurrency/ch06-async-io-coroutine/01-async-programming-evolution.md @@ -1,26 +1,29 @@ --- -title: "异步编程演进:从回调地狱到协程" -description: "梳理异步编程范式的演进脉络——回调、future 链、协程,理解每种模型的动机、痛点与 C++ 中的实现形态" chapter: 6 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 异步编程 - - 基础 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 梳理异步编程范式的演进脉络——回调、future 链、协程,理解每种模型的动机、痛点与 C++ 中的实现形态 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "线程池设计" - - "promise 与 packaged_task" +- 线程池设计 +- promise 与 packaged_task +reading_time_minutes: 21 related: - - "C++20 协程基础" - - "异步 I/O 与事件循环" +- C++20 协程基础 +- 异步 I/O 与事件循环 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +- 基础 +title: 异步编程演进:从回调地狱到协程 --- - # 异步编程演进:从回调地狱到协程 > 📖 **前置阅读**:这一篇会用到 C++20 协程。如果你还没接触过 `co_await`/`co_return`、`promise_type` 这些底层机制,可以先翻 [卷四·协程基础](../../vol4-advanced/01-coroutine-basics.md)——那里从零拆解了协程的"骨架"是怎么搭起来的。 diff --git a/documents/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md b/documents/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md index 27b03277e..ed49a6b07 100644 --- a/documents/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md +++ b/documents/vol5-concurrency/ch06-async-io-coroutine/02-coroutine-basics.md @@ -1,25 +1,25 @@ --- -title: "C++20 协程基础" -description: "深入 C++20 协程的语法、状态机模型与生命周期管理,理解 co_await/co_yield/co_return 的编译器变换" chapter: 6 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - coroutine - - 异步编程 +cpp_standard: +- 20 +description: 深入 C++20 协程的语法、状态机模型与生命周期管理,理解 co_await/co_yield/co_return 的编译器变换 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 30 -cpp_standard: [20] prerequisites: - - "异步编程演进:从回调地狱到协程" +- 异步编程演进:从回调地狱到协程 +reading_time_minutes: 25 related: - - "promise_type 与 awaitable" - - "异步 I/O 与事件循环" +- promise_type 与 awaitable +- 异步 I/O 与事件循环 +tags: +- host +- cpp-modern +- intermediate +- coroutine +- 异步编程 +title: C++20 协程基础 --- - # C++20 协程基础 上一篇我们看到协程让异步代码看起来像同步代码——线性流程,没有嵌套,没有回调金字塔。那篇的重点是"为什么需要协程"和"协程长什么样",我们只是展示了最终效果,但没解释它背后到底发生了什么。这一篇我们要把协程从里到外拆清楚:编译器对协程函数做了什么变换?协程帧里存了什么?`coroutine_handle` 是怎么管理协程生命周期的?这些问题的答案构成了理解 C++20 协程的基础。 diff --git a/documents/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md b/documents/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md index f1e63f512..65f6468c4 100644 --- a/documents/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md +++ b/documents/vol5-concurrency/ch06-async-io-coroutine/03-promise-type-and-awaitable.md @@ -1,25 +1,25 @@ --- -title: "promise_type 与 awaitable" -description: "掌握 C++20 协程的两大定制扩展点——promise_type 控制协程行为,awaitable 控制挂起与恢复" chapter: 6 -order: 3 -tags: - - host - - cpp-modern - - advanced - - coroutine - - 异步编程 +cpp_standard: +- 20 +description: 掌握 C++20 协程的两大定制扩展点——promise_type 控制协程行为,awaitable 控制挂起与恢复 difficulty: advanced +order: 3 platform: host -reading_time_minutes: 30 -cpp_standard: [20] prerequisites: - - "C++20 协程基础" +- C++20 协程基础 +reading_time_minutes: 28 related: - - "异步 I/O 与事件循环" - - "协程 Echo Server 实战" +- 异步 I/O 与事件循环 +- 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- advanced +- coroutine +- 异步编程 +title: promise_type 与 awaitable --- - # promise_type 与 awaitable 上一篇我们看到了 C++20 协程的基本语法——`co_await`、`co_yield`、`co_return` 长什么样,编译器帮我们生成了一个什么样的状态机。但说实话,光会用那几个关键字只是皮毛。C++20 协程真正的威力——或者说真正的"坑"——在于它把几乎所有的行为决策都交给了两个定制点:`promise_type` 和 `awaitable`(更准确地说是 awaiter)。这让协程变成了一个"框架"而不是一个"功能":语言标准只规定了编译器在什么时候调用什么方法,至于这些方法怎么实现,完全由你决定。 diff --git a/documents/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md b/documents/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md index 5e30924d1..7518515a6 100644 --- a/documents/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md +++ b/documents/vol5-concurrency/ch06-async-io-coroutine/04-async-io-and-event-loop.md @@ -1,25 +1,25 @@ --- -title: "异步 I/O 与事件循环" -description: "理解 I/O 多路复用(epoll/io_uring)的工作原理,构建协程驱动的事件循环,打通异步 I/O 的最后一公里" chapter: 6 -order: 4 -tags: - - host - - cpp-modern - - advanced - - coroutine - - 异步编程 +cpp_standard: +- 20 +description: 理解 I/O 多路复用(epoll/io_uring)的工作原理,构建协程驱动的事件循环,打通异步 I/O 的最后一公里 difficulty: advanced +order: 4 platform: host -reading_time_minutes: 35 -cpp_standard: [20] prerequisites: - - "promise_type 与 awaitable" - - "CPU cache 与 OS 线程" +- promise_type 与 awaitable +- CPU cache 与 OS 线程 +reading_time_minutes: 25 related: - - "协程 Echo Server 实战" +- 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- advanced +- coroutine +- 异步编程 +title: 异步 I/O 与事件循环 --- - # 异步 I/O 与事件循环 前面我们搞清楚了 C++20 协程的内部机制——`promise_type` 控制生命周期,awaiter/awaitable 控制挂起与恢复,调度器通过 `await_suspend` 拿到协程 handle 来管理执行时机。但说实话,到目前为止我们写的调度器只是一个"就绪队列"——它不知道什么叫"等数据到达",不知道什么叫"等网络连接就绪",更不知道什么叫"等定时器到期"。 diff --git a/documents/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md b/documents/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md index c3fa77af3..b7138d06b 100644 --- a/documents/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md +++ b/documents/vol5-concurrency/ch06-async-io-coroutine/05-coroutine-echo-server.md @@ -1,26 +1,26 @@ --- -title: "实战:协程 Echo Server" -description: "用 C++20 协程和自研事件循环实现一个完整的 TCP Echo Server,串联前四篇的所有知识点" chapter: 6 -order: 5 -tags: - - host - - cpp-modern - - advanced - - coroutine - - 异步编程 - - 实战 +cpp_standard: +- 20 +description: 用 C++20 协程和自研事件循环实现一个完整的 TCP Echo Server,串联前四篇的所有知识点 difficulty: advanced +order: 5 platform: host -reading_time_minutes: 35 -cpp_standard: [20] prerequisites: - - "异步 I/O 与事件循环" - - "promise_type 与 awaitable" +- 异步 I/O 与事件循环 +- promise_type 与 awaitable +reading_time_minutes: 40 related: - - "Actor 模型与消息传递" +- Actor 模型与消息传递 +tags: +- host +- cpp-modern +- advanced +- coroutine +- 异步编程 +- 实战 +title: 实战:协程 Echo Server --- - # 实战:协程 Echo Server 四篇理论铺垫——从异步编程范式的演进,到 C++20 协程基础,到 `promise_type` 和 awaitable 的定制机制,再到上篇把协程和 epoll 事件循环接通——我们终于走到了实战这一步。说实话,前面的每一篇都在为这一刻做准备:我们要用自己搭的协程框架写一个真正能跑的网络程序——一个 TCP Echo Server。 diff --git a/documents/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md b/documents/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md index 44852ac7e..b4a6dff4b 100644 --- a/documents/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md +++ b/documents/vol5-concurrency/ch07-actor-channel/02-channel-and-csp.md @@ -1,25 +1,26 @@ --- -title: "Channel 与 CSP 模型" -description: "理解 CSP(Communicating Sequential Processes)并发模型,用 C++ 实现类 Go channel 的通信管道" chapter: 7 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - 异步编程 - - 进阶 +cpp_standard: +- 17 +- 20 +description: 理解 CSP(Communicating Sequential Processes)并发模型,用 C++ 实现类 Go channel 的通信管道 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 25 -cpp_standard: [17, 20] prerequisites: - - "Actor 模型与消息传递" - - "线程安全队列" +- Actor 模型与消息传递 +- 线程安全队列 +reading_time_minutes: 24 related: - - "协程 Echo Server 实战" +- 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- intermediate +- 异步编程 +- 进阶 +title: Channel 与 CSP 模型 --- - # Channel 与 CSP 模型 上一篇我们聊了 Actor 模型——用有身份的 Actor + 异步消息传递来组织并发。这一篇我们看另一个同样主张"不共享内存"的流派:CSP(Communicating Sequential Processes,通信顺序进程)。 diff --git a/documents/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md b/documents/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md index 9df6cfe19..954a2c6f3 100644 --- a/documents/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md +++ b/documents/vol5-concurrency/ch08-debug-testing-perf/01-debugging-concurrency.md @@ -1,25 +1,28 @@ --- -title: "并发程序调试技巧" -description: "掌握 ThreadSanitizer、Helgrind 等工具的使用方法,建立并发 bug 的系统性诊断流程" chapter: 8 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 进阶 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 掌握 ThreadSanitizer、Helgrind 等工具的使用方法,建立并发 bug 的系统性诊断流程 difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 25 -cpp_standard: [11, 14, 17, 20] prerequisites: - - "mutex 与 RAII 锁" - - "原子操作" - - "线程安全队列" +- mutex 与 RAII 锁 +- 原子操作 +- 线程安全队列 +reading_time_minutes: 26 related: - - "并发性能测试与基准" +- 并发性能测试与基准 +tags: +- host +- cpp-modern +- intermediate +- 进阶 +title: 并发程序调试技巧 --- - # 并发程序调试技巧 说实话,调试并发程序的痛苦程度,只有亲自踩过坑的人才能理解。单线程程序的 bug 好歹是确定性的——你给定同样的输入,它一定在同一个地方以同样的方式炸掉。但并发 bug 不是这样。数据竞争可能跑一万次才出现一次,死锁可能只在某个特定的线程调度顺序下才触发,而且它总是"在你这里没问题,在 CI 上必挂"。笔者曾经为一个 data race 搞了整整两天,最后发现是一个 lambda 捕获了局部变量的引用——这种 bug 你光看代码根本看不出来,因为单线程执行路径下它完全正确。 diff --git a/documents/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md b/documents/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md index c701ccfa0..2bd78dd85 100644 --- a/documents/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md +++ b/documents/vol5-concurrency/ch08-debug-testing-perf/02-concurrency-benchmarks.md @@ -1,27 +1,28 @@ --- -title: "并发性能测试与基准" -description: "掌握 Google Benchmark 的使用方法,避开并发基准测试中的常见陷阱,学会用性能计数器定位瓶颈" chapter: 8 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - atomic - - mutex - - 优化 - - 进阶 +cpp_standard: +- 17 +- 20 +description: 掌握 Google Benchmark 的使用方法,避开并发基准测试中的常见陷阱,学会用性能计数器定位瓶颈 difficulty: intermediate +order: 2 platform: host -reading_time_minutes: 25 -cpp_standard: [17, 20] prerequisites: - - "并发程序调试技巧" - - "线程池" +- 并发程序调试技巧 +- 线程池 +reading_time_minutes: 20 related: - - "CPU cache 与 OS 线程" +- CPU cache 与 OS 线程 +tags: +- host +- cpp-modern +- intermediate +- atomic +- mutex +- 优化 +- 进阶 +title: 并发性能测试与基准 --- - # 并发性能测试与基准 > 📖 **深入阅读**:这篇只讲并发场景下的基准测试。更通用的性能工程——benchmark 方法论、cache 友好性、SIMD/AVX、读汇编——是 [卷六·性能工程](../../vol6-performance/index.md)的主场。 diff --git a/documents/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md b/documents/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md index f7733d110..fbb265c13 100644 --- a/documents/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md +++ b/documents/vol5-concurrency/ch09-distributed-bridge/01-from-concurrent-to-distributed.md @@ -1,28 +1,29 @@ --- -title: "从单机并发到分布式" -description: "理解单机并发与分布式系统的根本差异——局部失败、网络不可靠、时钟不一致,以及这些差异如何影响并发模型的选择" chapter: 9 -order: 1 -tags: - - host - - cpp-modern - - advanced - - 进阶 - - 异步编程 - - atomic - - mutex +cpp_standard: +- 17 +- 20 +description: 理解单机并发与分布式系统的根本差异——局部失败、网络不可靠、时钟不一致,以及这些差异如何影响并发模型的选择 difficulty: advanced +order: 1 platform: host -reading_time_minutes: 25 -cpp_standard: [17, 20] prerequisites: - - "Actor 模型与消息传递" - - "Channel 与 CSP 模型" - - "并发程序调试技巧" +- Actor 模型与消息传递 +- Channel 与 CSP 模型 +- 并发程序调试技巧 +reading_time_minutes: 24 related: - - "分布式一致性原语初探" +- 分布式一致性原语初探 +tags: +- host +- cpp-modern +- advanced +- 进阶 +- 异步编程 +- atomic +- mutex +title: 从单机并发到分布式 --- - # 从单机并发到分布式 > ℹ️ **本节定位**:这一章是概念导览,不配可运行代码、也不引入外部框架。目的是让你在进入卷八的分布式实战之前,先把"单机并发 → 分布式"的认知框架搭起来——知道哪些旧经验还能用,哪些得推倒重来。 diff --git a/documents/vol5-concurrency/ch09-distributed-bridge/02-distributed-primitives.md b/documents/vol5-concurrency/ch09-distributed-bridge/02-distributed-primitives.md index fab490382..fa3040b65 100644 --- a/documents/vol5-concurrency/ch09-distributed-bridge/02-distributed-primitives.md +++ b/documents/vol5-concurrency/ch09-distributed-bridge/02-distributed-primitives.md @@ -1,26 +1,27 @@ --- -title: "分布式一致性原语初探" -description: "从线性一致性到因果一致性,理解一致性模型谱系与 Paxos/Raft 核心思想,用 gRPC + C++20 协程搭建分布式通信骨架" chapter: 9 -order: 2 -tags: - - host - - cpp-modern - - advanced - - 进阶 - - 异步编程 - - atomic +cpp_standard: +- 17 +- 20 +description: 从线性一致性到因果一致性,理解一致性模型谱系与 Paxos/Raft 核心思想,用 gRPC + C++20 协程搭建分布式通信骨架 difficulty: advanced +order: 2 platform: host -reading_time_minutes: 30 -cpp_standard: [17, 20] prerequisites: - - "从单机并发到分布式" - - "promise_type 与 awaitable" +- 从单机并发到分布式 +- promise_type 与 awaitable +reading_time_minutes: 31 related: - - "协程 Echo Server 实战" +- 协程 Echo Server 实战 +tags: +- host +- cpp-modern +- advanced +- 进阶 +- 异步编程 +- atomic +title: 分布式一致性原语初探 --- - # 分布式一致性原语初探 > ℹ️ **本节定位**:承接上一篇,继续概念导览。这里讲的一致性模型谱系同样不配可运行代码,重在帮你建立"从强一致到弱一致"的直觉,为日后读分布式论文和卷八实战打底。 diff --git a/documents/vol5-concurrency/exercises/00-thread-lifecycle.md b/documents/vol5-concurrency/exercises/00-thread-lifecycle.md index 5122317db..f65d8c403 100644 --- a/documents/vol5-concurrency/exercises/00-thread-lifecycle.md +++ b/documents/vol5-concurrency/exercises/00-thread-lifecycle.md @@ -1,21 +1,22 @@ --- -title: "Lab 0: Thread Lifecycle Lab" -description: "通过并行文件扫描器,训练线程创建、RAII 包装、参数生命周期和 thread_local 统计的实战能力" chapter: 10 -order: 0 +cpp_standard: +- 17 +- 20 +description: 通过并行文件扫描器,训练线程创建、RAII 包装、参数生命周期和 thread_local 统计的实战能力 difficulty: intermediate -tags: - - host - - cpp-modern - - atomic - - beginner -cpp_standard: [17, 20] -reading_time_minutes: 45 +order: 0 prerequisites: - - "卷五 ch00: 并发思维与基础" - - "卷五 ch01: 线程生命周期与 RAII" +- '卷五 ch00: 并发思维与基础' +- '卷五 ch01: 线程生命周期与 RAII' +reading_time_minutes: 23 +tags: +- host +- cpp-modern +- atomic +- beginner +title: 'Lab 0: Thread Lifecycle Lab' --- - # Lab 0: Thread Lifecycle Lab ## 目标 diff --git a/documents/vol5-concurrency/exercises/01-bounded-queue.md b/documents/vol5-concurrency/exercises/01-bounded-queue.md index 475e1f7e0..254e90d4d 100644 --- a/documents/vol5-concurrency/exercises/01-bounded-queue.md +++ b/documents/vol5-concurrency/exercises/01-bounded-queue.md @@ -1,23 +1,24 @@ --- -title: "Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives" -description: "通过阻塞队列、分片缓存和 C++20 同步原语实践,掌握 mutex、condition_variable、关闭语义和背压策略" chapter: 10 -order: 1 +cpp_standard: +- 17 +- 20 +description: 通过阻塞队列、分片缓存和 C++20 同步原语实践,掌握 mutex、condition_variable、关闭语义和背压策略 difficulty: intermediate -tags: - - host - - cpp-modern - - mutex - - intermediate -cpp_standard: [17, 20] -reading_time_minutes: 29 +order: 1 prerequisites: - - "卷五 ch00: 并发思维与基础" - - "卷五 ch01: 线程生命周期与 RAII" - - "卷五 ch02: 互斥量、条件变量与同步原语" - - "Lab 0: Thread Lifecycle Lab" +- '卷五 ch00: 并发思维与基础' +- '卷五 ch01: 线程生命周期与 RAII' +- '卷五 ch02: 互斥量、条件变量与同步原语' +- 'Lab 0: Thread Lifecycle Lab' +reading_time_minutes: 21 +tags: +- host +- cpp-modern +- mutex +- intermediate +title: 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' --- - # Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives ## 目标 diff --git a/documents/vol5-concurrency/exercises/02-atomic-spsc.md b/documents/vol5-concurrency/exercises/02-atomic-spsc.md index a6b3f8c17..12a8102ce 100644 --- a/documents/vol5-concurrency/exercises/02-atomic-spsc.md +++ b/documents/vol5-concurrency/exercises/02-atomic-spsc.md @@ -1,22 +1,23 @@ --- -title: "Lab 2: Atomic Metrics and SPSC Ring Buffer" -description: "通过原子计数器和单生产者单消费者环形缓冲区,掌握 atomic、memory_order、false sharing 和基准测试方法论" chapter: 10 -order: 2 +cpp_standard: +- 17 +- 20 +description: 通过原子计数器和单生产者单消费者环形缓冲区,掌握 atomic、memory_order、false sharing 和基准测试方法论 difficulty: intermediate -tags: - - host - - cpp-modern - - atomic - - memory_order - - intermediate -cpp_standard: [17, 20] -reading_time_minutes: 16 +order: 2 prerequisites: - - "卷五 ch03: 原子操作与内存模型" - - "Lab 0: Thread Lifecycle Lab" +- '卷五 ch03: 原子操作与内存模型' +- 'Lab 0: Thread Lifecycle Lab' +reading_time_minutes: 14 +tags: +- host +- cpp-modern +- atomic +- memory_order +- intermediate +title: 'Lab 2: Atomic Metrics and SPSC Ring Buffer' --- - # Lab 2: Atomic Metrics and SPSC Ring Buffer ## 目标 diff --git a/documents/vol5-concurrency/exercises/02.5-debugging.md b/documents/vol5-concurrency/exercises/02.5-debugging.md index d3e5e251c..8692ded1a 100644 --- a/documents/vol5-concurrency/exercises/02.5-debugging.md +++ b/documents/vol5-concurrency/exercises/02.5-debugging.md @@ -1,22 +1,23 @@ --- -title: "Lab 2.5: Concurrency Debugging Lab" -description: "通过定位和修复 5 个故意植入的并发缺陷,训练 TSan、helgrind 和性能诊断的实战调试能力" chapter: 10 -order: 3 +cpp_standard: +- 17 +- 20 +description: 通过定位和修复 5 个故意植入的并发缺陷,训练 TSan、helgrind 和性能诊断的实战调试能力 difficulty: intermediate -tags: - - host - - cpp-modern - - intermediate -cpp_standard: [17, 20] -reading_time_minutes: 11 +order: 3 prerequisites: - - "卷五 ch08: 调试、测试与性能" - - "Lab 0: Thread Lifecycle Lab" - - "Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives" - - "Lab 2: Atomic Metrics and SPSC Ring Buffer" +- '卷五 ch08: 调试、测试与性能' +- 'Lab 0: Thread Lifecycle Lab' +- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +- 'Lab 2: Atomic Metrics and SPSC Ring Buffer' +reading_time_minutes: 8 +tags: +- host +- cpp-modern +- intermediate +title: 'Lab 2.5: Concurrency Debugging Lab' --- - # Lab 2.5: Concurrency Debugging Lab ## 目标 diff --git a/documents/vol5-concurrency/exercises/03-thread-pool.md b/documents/vol5-concurrency/exercises/03-thread-pool.md index a66c567ee..62ccbcf56 100644 --- a/documents/vol5-concurrency/exercises/03-thread-pool.md +++ b/documents/vol5-concurrency/exercises/03-thread-pool.md @@ -1,21 +1,22 @@ --- -title: "Lab 3: Production-style Thread Pool" -description: "实现固定大小线程池,掌握 future、packaged_task、异常传播、优雅关闭和背压策略" chapter: 10 -order: 4 +cpp_standard: +- 17 +- 20 +description: 实现固定大小线程池,掌握 future、packaged_task、异常传播、优雅关闭和背压策略 difficulty: advanced -tags: - - host - - cpp-modern - - advanced -cpp_standard: [17, 20] -reading_time_minutes: 16 +order: 4 prerequisites: - - "卷五 ch05: future、任务与线程池" - - "Lab 0: Thread Lifecycle Lab" - - "Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives" +- '卷五 ch05: future、任务与线程池' +- 'Lab 0: Thread Lifecycle Lab' +- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +reading_time_minutes: 12 +tags: +- host +- cpp-modern +- advanced +title: 'Lab 3: Production-style Thread Pool' --- - # Lab 3: Production-style Thread Pool ## 目标 diff --git a/documents/vol5-concurrency/exercises/04-coroutine-scheduler.md b/documents/vol5-concurrency/exercises/04-coroutine-scheduler.md index fea2c2db2..678427d93 100644 --- a/documents/vol5-concurrency/exercises/04-coroutine-scheduler.md +++ b/documents/vol5-concurrency/exercises/04-coroutine-scheduler.md @@ -1,21 +1,21 @@ --- -title: "Lab 4: Coroutine Scheduler and Event Loop" -description: "实现极简协程调度器,掌握 C++20 协程从语法到运行时的完整链路:Task、Scheduler、timer、epoll 事件循环" chapter: 10 -order: 5 +cpp_standard: +- 20 +description: 实现极简协程调度器,掌握 C++20 协程从语法到运行时的完整链路:Task、Scheduler、timer、epoll 事件循环 difficulty: advanced -tags: - - host - - cpp-modern - - coroutine - - advanced -cpp_standard: [20] -reading_time_minutes: 18 +order: 5 prerequisites: - - "卷五 ch06: 异步 I/O 与协程" - - "Lab 3: Production-style Thread Pool" +- '卷五 ch06: 异步 I/O 与协程' +- 'Lab 3: Production-style Thread Pool' +reading_time_minutes: 14 +tags: +- host +- cpp-modern +- coroutine +- advanced +title: 'Lab 4: Coroutine Scheduler and Event Loop' --- - # Lab 4: Coroutine Scheduler and Event Loop ## 目标 diff --git a/documents/vol5-concurrency/exercises/05-channel-actor.md b/documents/vol5-concurrency/exercises/05-channel-actor.md index 425fd5964..3df2f8f4d 100644 --- a/documents/vol5-concurrency/exercises/05-channel-actor.md +++ b/documents/vol5-concurrency/exercises/05-channel-actor.md @@ -1,22 +1,22 @@ --- -title: "Lab 5: Channel or Actor Runtime" -description: "通过 Channel 或 Actor 模式实践消息传递并发,掌握 CSP、mailbox、select 和取消语义" chapter: 10 -order: 6 +cpp_standard: +- 20 +description: 通过 Channel 或 Actor 模式实践消息传递并发,掌握 CSP、mailbox、select 和取消语义 difficulty: advanced -tags: - - host - - cpp-modern - - coroutine - - advanced -cpp_standard: [20] -reading_time_minutes: 13 +order: 6 prerequisites: - - "卷五 ch07: Actor 与 Channel" - - "Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives" - - "Lab 4: Coroutine Scheduler and Event Loop" +- '卷五 ch07: Actor 与 Channel' +- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +- 'Lab 4: Coroutine Scheduler and Event Loop' +reading_time_minutes: 10 +tags: +- host +- cpp-modern +- coroutine +- advanced +title: 'Lab 5: Channel or Actor Runtime' --- - # Lab 5: Channel or Actor Runtime ## 目标 diff --git a/documents/vol5-concurrency/exercises/06-capstone-mini-runtime.md b/documents/vol5-concurrency/exercises/06-capstone-mini-runtime.md index efda03a91..660278813 100644 --- a/documents/vol5-concurrency/exercises/06-capstone-mini-runtime.md +++ b/documents/vol5-concurrency/exercises/06-capstone-mini-runtime.md @@ -1,26 +1,26 @@ --- -title: "Capstone: Mini Concurrent Runtime" -description: "组合卷五所有 Lab 的组件,构建一个 mini 并发运行时,训练系统设计、组件组合和可观测性" chapter: 10 -order: 7 +cpp_standard: +- 20 +description: 组合卷五所有 Lab 的组件,构建一个 mini 并发运行时,训练系统设计、组件组合和可观测性 difficulty: advanced -tags: - - host - - cpp-modern - - coroutine - - advanced -cpp_standard: [20] -reading_time_minutes: 9 +order: 7 prerequisites: - - "Lab 0: Thread Lifecycle Lab" - - "Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives" - - "Lab 2: Atomic Metrics and SPSC Ring Buffer" - - "Lab 2.5: Concurrency Debugging Lab" - - "Lab 3: Production-style Thread Pool" - - "Lab 4: Coroutine Scheduler and Event Loop" - - "Lab 5: Channel or Actor Runtime" +- 'Lab 0: Thread Lifecycle Lab' +- 'Lab 1: Bounded Queue, Concurrent Cache and Sync Primitives' +- 'Lab 2: Atomic Metrics and SPSC Ring Buffer' +- 'Lab 2.5: Concurrency Debugging Lab' +- 'Lab 3: Production-style Thread Pool' +- 'Lab 4: Coroutine Scheduler and Event Loop' +- 'Lab 5: Channel or Actor Runtime' +reading_time_minutes: 7 +tags: +- host +- cpp-modern +- coroutine +- advanced +title: 'Capstone: Mini Concurrent Runtime' --- - # Capstone: Mini Concurrent Runtime ## 目标 diff --git a/documents/vol6-performance/02-inline-and-compiler-optimization.md b/documents/vol6-performance/02-inline-and-compiler-optimization.md index a14548e60..a6c1673cd 100644 --- a/documents/vol6-performance/02-inline-and-compiler-optimization.md +++ b/documents/vol6-performance/02-inline-and-compiler-optimization.md @@ -70,7 +70,7 @@ title: 内联与编译器优化 和 ObserverPtr" chapter: 1 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 智能指针 - - 内存管理 +cpp_standard: +- 17 +- 20 +description: 理解 C++ 中借用、观察与非拥有指针的语义边界,手搓 Borrowed 和 ObserverPtr difficulty: intermediate +order: 1 platform: host -reading_time_minutes: 20 prerequisites: - - "卷二 · 第一章:RAII 深入理解" - - "卷二 · 第一章:weak_ptr 与循环引用" +- 卷二 · 第一章:RAII 深入理解 +- 卷二 · 第一章:weak_ptr 与循环引用 +reading_time_minutes: 13 related: - - "WeakPtr 反模式:T* + raw Flag* 的致命陷阱" -cpp_standard: [17, 20] +- WeakPtr 反模式:T* + raw Flag* 的致命陷阱 +tags: +- host +- cpp-modern +- intermediate +- 智能指针 +- 内存管理 +title: 非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr --- - # 非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr ## 引言 diff --git a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md index a760829bd..af85fc223 100644 --- a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md +++ b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/02-unsafe-weakptr-ub.md @@ -1,24 +1,25 @@ --- -title: "WeakPtr 反模式:T* + raw Flag* 的致命陷阱" -description: "深入分析 T* + raw Flag* 为什么不是可靠 WeakPtr,用最小示例复现 UB" chapter: 1 -order: 2 -tags: - - host - - cpp-modern - - advanced - - 智能指针 - - 引用计数 +cpp_standard: +- 17 +- 20 +description: 深入分析 T* + raw Flag* 为什么不是可靠 WeakPtr,用最小示例复现 UB difficulty: advanced +order: 2 platform: host -reading_time_minutes: 11 prerequisites: - - "非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr" +- 非拥有指针全景:从 T* 到 Borrowed 到 ObserverPtr +reading_time_minutes: 8 related: - - "SimpleWeakPtr:T* + shared_ptr 的安全改进" -cpp_standard: [17, 20] +- SimpleWeakPtr:T* + shared_ptr 的安全改进 +tags: +- host +- cpp-modern +- advanced +- 智能指针 +- 引用计数 +title: WeakPtr 反模式:T* + raw Flag* 的致命陷阱 --- - # WeakPtr 反模式:`T* + raw Flag*` 的致命陷阱 ## 引言 diff --git a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md index 430a62f83..2481c6958 100644 --- a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md +++ b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/03-simple-weakptr.md @@ -1,24 +1,25 @@ --- -title: "SimpleWeakPtr:T* + shared_ptr 的安全改进" -description: "用 shared_ptr 构建 control block,实现对象销毁后的安全判空" chapter: 1 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - 智能指针 - - 引用计数 +cpp_standard: +- 17 +- 20 +description: 用 shared_ptr 构建 control block,实现对象销毁后的安全判空 difficulty: intermediate +order: 3 platform: host -reading_time_minutes: 8 prerequisites: - - "WeakPtr 反模式:T* + raw Flag* 的致命陷阱" +- WeakPtr 反模式:T* + raw Flag* 的致命陷阱 +reading_time_minutes: 6 related: - - "Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory" -cpp_standard: [17, 20] +- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory +tags: +- host +- cpp-modern +- intermediate +- 智能指针 +- 引用计数 +title: SimpleWeakPtr:T* + shared_ptr 的安全改进 --- - # SimpleWeakPtr:T* + shared_ptr\ 的安全改进 ## 引言 diff --git a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md index 0501334b4..f9590ab04 100644 --- a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md +++ b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/04-chrome-weakptr.md @@ -1,25 +1,26 @@ --- -title: "Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory" -description: "实现教学版 Chrome WeakPtr,理解 ref-counted control block 与序列绑定模型" chapter: 1 -order: 4 -tags: - - host - - cpp-modern - - advanced - - 智能指针 - - 引用计数 - - 回调机制 +cpp_standard: +- 17 +- 20 +description: 实现教学版 Chrome WeakPtr,理解 ref-counted control block 与序列绑定模型 difficulty: advanced +order: 4 platform: host -reading_time_minutes: 12 prerequisites: - - "SimpleWeakPtr:T* + shared_ptr 的安全改进" +- SimpleWeakPtr:T* + shared_ptr 的安全改进 +reading_time_minutes: 9 related: - - "std::weak_ptr 对比与异步回调实战" -cpp_standard: [17, 20] +- std::weak_ptr 对比与异步回调实战 +tags: +- host +- cpp-modern +- advanced +- 智能指针 +- 引用计数 +- 回调机制 +title: Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory --- - # Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory ## 引言 diff --git a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md index 2b9adb0b6..97b02ce97 100644 --- a/documents/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md +++ b/documents/vol8-domains/cpp-deep-dives/pointer-semantics/05-weakptr-comparison-and-async.md @@ -1,26 +1,27 @@ --- -title: "std::weak_ptr 对比与异步回调实战" -description: "对比 std::weak_ptr 与 Chrome WeakPtr,六种异步回调捕获模式的安全分析" chapter: 1 -order: 5 -tags: - - host - - cpp-modern - - advanced - - 智能指针 - - 异步编程 - - 回调机制 +cpp_standard: +- 17 +- 20 +description: 对比 std::weak_ptr 与 Chrome WeakPtr,六种异步回调捕获模式的安全分析 difficulty: advanced +order: 5 platform: host -reading_time_minutes: 8 prerequisites: - - "Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory" - - "卷二 · 第一章:weak_ptr 与循环引用" +- Chrome-like WeakPtr:引用计数控制块与 WeakPtrFactory +- 卷二 · 第一章:weak_ptr 与循环引用 +reading_time_minutes: 7 related: - - "跨线程安全、性能取舍与设计原则总结" -cpp_standard: [17, 20] +- 跨线程安全、性能取舍与设计原则总结 +tags: +- host +- cpp-modern +- advanced +- 智能指针 +- 异步编程 +- 回调机制 +title: std::weak_ptr 对比与异步回调实战 --- - # std::weak_ptr 对比与异步回调实战 ## 引言 diff --git a/documents/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md b/documents/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md index 6fdf0540a..91b314f67 100644 --- a/documents/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md +++ b/documents/vol8-domains/embedded/00-env-setup/01-toolchain-setup.md @@ -1,14 +1,15 @@ --- -title: 第1篇:从零搭建 STM32 开发工具链 —— 交叉编译原理与安装指南 -description: '' +chapter: 14 +difficulty: beginner +order: 1 +platform: stm32f1 +reading_time_minutes: 11 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 1 +title: 第1篇:从零搭建 STM32 开发工具链 —— 交叉编译原理与安装指南 +description: '' --- # 第1篇:从零搭建 STM32 开发工具链 —— 交叉编译原理与安装指南 diff --git a/documents/vol8-domains/embedded/00-env-setup/02-project-structure.md b/documents/vol8-domains/embedded/00-env-setup/02-project-structure.md index 62de8d0b2..6ff153762 100644 --- a/documents/vol8-domains/embedded/00-env-setup/02-project-structure.md +++ b/documents/vol8-domains/embedded/00-env-setup/02-project-structure.md @@ -1,14 +1,15 @@ --- -title: 第2篇:项目结构篇 —— HAL 库获取、启动文件坑位与目录搭建 -description: '' +chapter: 14 +difficulty: beginner +order: 2 +platform: stm32f1 +reading_time_minutes: 15 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 2 +title: 第2篇:项目结构篇 —— HAL 库获取、启动文件坑位与目录搭建 +description: '' --- # 第2篇:项目结构篇 —— HAL 库获取、启动文件坑位与目录搭建 diff --git a/documents/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md b/documents/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md index 58dc6b079..afb0affd4 100644 --- a/documents/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md +++ b/documents/vol8-domains/embedded/00-env-setup/03-cmake-configuration.md @@ -1,14 +1,15 @@ --- -title: CMake 配置篇 —— 从零构建 STM32 构建系统 -description: '' +chapter: 14 +difficulty: beginner +order: 3 +platform: stm32f1 +reading_time_minutes: 17 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 3 +title: CMake 配置篇 —— 从零构建 STM32 构建系统 +description: '' --- # CMake 配置篇 —— 从零构建 STM32 构建系统 diff --git a/documents/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md b/documents/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md index f1fb3b7bb..44fdefd2b 100644 --- a/documents/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md +++ b/documents/vol8-domains/embedded/00-env-setup/04-wsl2-usb.md @@ -1,14 +1,15 @@ --- -title: 环境搭建(四):WSL2 USB 透传,让 ST-Link 穿越虚拟化边界 -description: '' +chapter: 14 +difficulty: beginner +order: 4 +platform: stm32f1 +reading_time_minutes: 14 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 4 +title: 环境搭建(四):WSL2 USB 透传,让 ST-Link 穿越虚拟化边界 +description: '' --- # 环境搭建(四):WSL2 USB 透传,让 ST-Link 穿越虚拟化边界 diff --git a/documents/vol8-domains/embedded/00-env-setup/05-debugging-guide.md b/documents/vol8-domains/embedded/00-env-setup/05-debugging-guide.md index 2c1864e09..20302fca5 100644 --- a/documents/vol8-domains/embedded/00-env-setup/05-debugging-guide.md +++ b/documents/vol8-domains/embedded/00-env-setup/05-debugging-guide.md @@ -1,14 +1,15 @@ --- -title: 第5篇:调试进阶篇 —— 从 printf 到完整 GDB 调试环境 -description: '' +chapter: 14 +difficulty: beginner +order: 5 +platform: stm32f1 +reading_time_minutes: 23 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 14 -order: 5 +title: 第5篇:调试进阶篇 —— 从 printf 到完整 GDB 调试环境 +description: '' --- # 第5篇:调试进阶篇 —— 从 printf 到完整 GDB 调试环境 diff --git a/documents/vol8-domains/embedded/01-dynamic-allocation-issues.md b/documents/vol8-domains/embedded/01-dynamic-allocation-issues.md index 35df2dd4d..99bee47ae 100644 --- a/documents/vol8-domains/embedded/01-dynamic-allocation-issues.md +++ b/documents/vol8-domains/embedded/01-dynamic-allocation-issues.md @@ -11,7 +11,7 @@ order: 1 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 6 +reading_time_minutes: 5 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/01-led/01-motivation-and-overview.md b/documents/vol8-domains/embedded/01-led/01-motivation-and-overview.md index 065dcb634..c9f5aa750 100644 --- a/documents/vol8-domains/embedded/01-led/01-motivation-and-overview.md +++ b/documents/vol8-domains/embedded/01-led/01-motivation-and-overview.md @@ -1,14 +1,15 @@ --- -title: 第6篇:从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32 -description: '' +chapter: 15 +difficulty: beginner +order: 1 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 1 +title: 第6篇:从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32 +description: '' --- # 第6篇:从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32 diff --git a/documents/vol8-domains/embedded/01-led/02-what-is-gpio.md b/documents/vol8-domains/embedded/01-led/02-what-is-gpio.md index 501613f5f..117dd6bd9 100644 --- a/documents/vol8-domains/embedded/01-led/02-what-is-gpio.md +++ b/documents/vol8-domains/embedded/01-led/02-what-is-gpio.md @@ -1,14 +1,15 @@ --- -title: 第7篇:GPIO到底是什么 —— 通用输入输出的前世今生 -description: '' +chapter: 15 +difficulty: beginner +order: 2 +platform: stm32f1 +reading_time_minutes: 24 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 2 +title: 第7篇:GPIO到底是什么 —— 通用输入输出的前世今生 +description: '' --- # 第7篇:GPIO到底是什么 —— 通用输入输出的前世今生 diff --git a/documents/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md b/documents/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md index 13a718d56..c238b838b 100644 --- a/documents/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md +++ b/documents/vol8-domains/embedded/01-led/03-output-modes-and-pc13.md @@ -1,14 +1,15 @@ --- -title: 第8篇:推挽、开漏与PC13 —— LED点亮的硬件秘密 -description: '' +chapter: 15 +difficulty: beginner +order: 3 +platform: stm32f1 +reading_time_minutes: 23 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 3 +title: 第8篇:推挽、开漏与PC13 —— LED点亮的硬件秘密 +description: '' --- # 第8篇:推挽、开漏与PC13 —— LED点亮的硬件秘密 diff --git a/documents/vol8-domains/embedded/01-led/04-hal-gpio-clock.md b/documents/vol8-domains/embedded/01-led/04-hal-gpio-clock.md index 8cf119566..62ff2e716 100644 --- a/documents/vol8-domains/embedded/01-led/04-hal-gpio-clock.md +++ b/documents/vol8-domains/embedded/01-led/04-hal-gpio-clock.md @@ -1,14 +1,15 @@ --- -title: 第9篇:HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅 -description: '' +chapter: 15 +difficulty: beginner +order: 4 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 4 +title: 第9篇:HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅 +description: '' --- # 第9篇:HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅 diff --git a/documents/vol8-domains/embedded/01-led/05-hal-gpio-init.md b/documents/vol8-domains/embedded/01-led/05-hal-gpio-init.md index 876385e2b..4430ef400 100644 --- a/documents/vol8-domains/embedded/01-led/05-hal-gpio-init.md +++ b/documents/vol8-domains/embedded/01-led/05-hal-gpio-init.md @@ -1,14 +1,15 @@ --- -title: 第10篇:HAL_GPIO_Init —— 把引脚配置告诉芯片的仪式 -description: '' +chapter: 15 +difficulty: beginner +order: 5 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 5 +title: 第10篇:HAL_GPIO_Init —— 把引脚配置告诉芯片的仪式 +description: '' --- # 第10篇:HAL_GPIO_Init —— 把引脚配置告诉芯片的仪式 diff --git a/documents/vol8-domains/embedded/01-led/06-hal-gpio-output.md b/documents/vol8-domains/embedded/01-led/06-hal-gpio-output.md index d543820b3..6bba0a1ca 100644 --- a/documents/vol8-domains/embedded/01-led/06-hal-gpio-output.md +++ b/documents/vol8-domains/embedded/01-led/06-hal-gpio-output.md @@ -1,14 +1,15 @@ --- -title: 第11篇:HAL_GPIO_WritePin与TogglePin —— 让引脚动起来 -description: '' +chapter: 15 +difficulty: beginner +order: 6 +platform: stm32f1 +reading_time_minutes: 9 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 6 +title: 第11篇:HAL_GPIO_WritePin与TogglePin —— 让引脚动起来 +description: '' --- # 第11篇:HAL_GPIO_WritePin与TogglePin —— 让引脚动起来 diff --git a/documents/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md b/documents/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md index 1c2c826ce..a5bcbb1c7 100644 --- a/documents/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md +++ b/documents/vol8-domains/embedded/01-led/07-c-macro-led-implementation.md @@ -1,14 +1,15 @@ --- -title: 第12篇:C宏时代的LED驱动 —— 能跑但不优雅 -description: '' +chapter: 15 +difficulty: beginner +order: 7 +platform: stm32f1 +reading_time_minutes: 21 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 7 +title: 第12篇:C宏时代的LED驱动 —— 能跑但不优雅 +description: '' --- # 第12篇:C宏时代的LED驱动 —— 能跑但不优雅 diff --git a/documents/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md b/documents/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md index 8d72b0c8c..1e668fbc1 100644 --- a/documents/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md +++ b/documents/vol8-domains/embedded/01-led/08-cpp-enum-class-revolution.md @@ -1,14 +1,15 @@ --- -title: 第13篇:第一次重构 —— enum class取代宏,类型安全的开始 -description: '' +chapter: 15 +difficulty: beginner +order: 8 +platform: stm32f1 +reading_time_minutes: 9 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 8 +title: 第13篇:第一次重构 —— enum class取代宏,类型安全的开始 +description: '' --- # 第13篇:第一次重构 —— enum class取代宏,类型安全的开始 diff --git a/documents/vol8-domains/embedded/01-led/09-cpp-template-gpio.md b/documents/vol8-domains/embedded/01-led/09-cpp-template-gpio.md index 6be828f90..4e451afbe 100644 --- a/documents/vol8-domains/embedded/01-led/09-cpp-template-gpio.md +++ b/documents/vol8-domains/embedded/01-led/09-cpp-template-gpio.md @@ -1,14 +1,15 @@ --- -title: 第14篇:第二次重构 —— 模板登场,编译时绑定端口和引脚 -description: '' +chapter: 15 +difficulty: beginner +order: 9 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 9 +title: 第14篇:第二次重构 —— 模板登场,编译时绑定端口和引脚 +description: '' --- # 第14篇:第二次重构 —— 模板登场,编译时绑定端口和引脚 diff --git a/documents/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md b/documents/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md index b9c5db57f..f128cd2ab 100644 --- a/documents/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md +++ b/documents/vol8-domains/embedded/01-led/10-cpp-if-constexpr-clock.md @@ -1,14 +1,15 @@ --- -title: 第15篇:第三次重构 —— if constexpr让时钟使能在编译时自动选对 -description: '' +chapter: 15 +difficulty: beginner +order: 10 +platform: stm32f1 +reading_time_minutes: 8 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 10 +title: 第15篇:第三次重构 —— if constexpr让时钟使能在编译时自动选对 +description: '' --- # 第15篇:第三次重构 —— if constexpr让时钟使能在编译时自动选对 diff --git a/documents/vol8-domains/embedded/01-led/11-cpp-led-template.md b/documents/vol8-domains/embedded/01-led/11-cpp-led-template.md index a16e57a6f..3eef4b80e 100644 --- a/documents/vol8-domains/embedded/01-led/11-cpp-led-template.md +++ b/documents/vol8-domains/embedded/01-led/11-cpp-led-template.md @@ -1,14 +1,15 @@ --- -title: 第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象 -description: '' +chapter: 15 +difficulty: beginner +order: 11 +platform: stm32f1 +reading_time_minutes: 25 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 11 +title: 第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象 +description: '' --- # 第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象 diff --git a/documents/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md b/documents/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md index 0c10c3725..743312752 100644 --- a/documents/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md +++ b/documents/vol8-domains/embedded/01-led/12-cpp23-attributes-and-features.md @@ -1,14 +1,15 @@ --- -title: 第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明 -description: '' +chapter: 15 +difficulty: beginner +order: 12 +platform: stm32f1 +reading_time_minutes: 11 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 12 +title: 第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明 +description: '' --- # 第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明 diff --git a/documents/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md b/documents/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md index d2209091c..109c833d4 100644 --- a/documents/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md +++ b/documents/vol8-domains/embedded/01-led/13-pitfalls-and-exercises.md @@ -1,14 +1,15 @@ --- -title: 第18篇:常见坑位与实战练习 —— 把LED玩出花样来 -description: '' +chapter: 15 +difficulty: beginner +order: 13 +platform: stm32f1 +reading_time_minutes: 10 tags: - beginner - cpp-modern - stm32f1 -difficulty: beginner -platform: stm32f1 -chapter: 15 -order: 13 +title: 第18篇:常见坑位与实战练习 —— 把LED玩出花样来 +description: '' --- # 第18篇:常见坑位与实战练习 —— 把LED玩出花样来 diff --git a/documents/vol8-domains/embedded/01-resource-and-realtime-constraints.md b/documents/vol8-domains/embedded/01-resource-and-realtime-constraints.md index d2955ae1e..cb3c8f46b 100644 --- a/documents/vol8-domains/embedded/01-resource-and-realtime-constraints.md +++ b/documents/vol8-domains/embedded/01-resource-and-realtime-constraints.md @@ -10,7 +10,7 @@ difficulty: beginner order: 1 platform: stm32f1 prerequisites: [] -reading_time_minutes: 12 +reading_time_minutes: 10 related: [] tags: - cpp-modern diff --git a/documents/vol8-domains/embedded/01-zero-overhead-abstraction.md b/documents/vol8-domains/embedded/01-zero-overhead-abstraction.md index 52b547a68..d0ba749fb 100644 --- a/documents/vol8-domains/embedded/01-zero-overhead-abstraction.md +++ b/documents/vol8-domains/embedded/01-zero-overhead-abstraction.md @@ -11,7 +11,7 @@ order: 1 platform: stm32f1 prerequisites: - 'Chapter 1: 构建工具链' -reading_time_minutes: 19 +reading_time_minutes: 15 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/02-button/01-from-output-to-input.md b/documents/vol8-domains/embedded/02-button/01-from-output-to-input.md index 6a41e0066..a3928a956 100644 --- a/documents/vol8-domains/embedded/02-button/01-from-output-to-input.md +++ b/documents/vol8-domains/embedded/02-button/01-from-output-to-input.md @@ -1,14 +1,15 @@ --- -title: 第19篇:从输出到输入 —— 为什么按钮比 LED 难 -description: '' +chapter: 16 +difficulty: intermediate +order: 1 +platform: stm32f1 +reading_time_minutes: 11 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 1 +title: 第19篇:从输出到输入 —— 为什么按钮比 LED 难 +description: '' --- # 第19篇:从输出到输入 —— 为什么按钮比 LED 难 diff --git a/documents/vol8-domains/embedded/02-button/02-gpio-input-circuits.md b/documents/vol8-domains/embedded/02-button/02-gpio-input-circuits.md index 3e2e53412..8a2605749 100644 --- a/documents/vol8-domains/embedded/02-button/02-gpio-input-circuits.md +++ b/documents/vol8-domains/embedded/02-button/02-gpio-input-circuits.md @@ -1,14 +1,15 @@ --- -title: '第20篇:GPIO 输入模式内部电路 —— 芯片是如何"听"到外部信号的' -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 16 +difficulty: intermediate order: 2 +platform: stm32f1 +reading_time_minutes: 10 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第20篇:GPIO 输入模式内部电路 —— 芯片是如何"听"到外部信号的 +description: '' --- # 第20篇:GPIO 输入模式内部电路 —— 芯片是如何"听"到外部信号的 diff --git a/documents/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md b/documents/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md index c83513e22..d26b1beb0 100644 --- a/documents/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md +++ b/documents/vol8-domains/embedded/02-button/03-button-hardware-and-bounce.md @@ -1,14 +1,15 @@ --- -title: 第21篇:按钮电路与机械抖动 —— 真实世界的信号长什么样 -description: '' +chapter: 16 +difficulty: intermediate +order: 3 +platform: stm32f1 +reading_time_minutes: 9 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 3 +title: 第21篇:按钮电路与机械抖动 —— 真实世界的信号长什么样 +description: '' --- # 第21篇:按钮电路与机械抖动 —— 真实世界的信号长什么样 diff --git a/documents/vol8-domains/embedded/02-button/04-hal-gpio-input.md b/documents/vol8-domains/embedded/02-button/04-hal-gpio-input.md index c65f5c00d..4b3e50a54 100644 --- a/documents/vol8-domains/embedded/02-button/04-hal-gpio-input.md +++ b/documents/vol8-domains/embedded/02-button/04-hal-gpio-input.md @@ -1,14 +1,15 @@ --- -title: 第22篇:HAL GPIO 输入 API —— 怎么用代码读到按钮状态 -description: '' +chapter: 16 +difficulty: intermediate +order: 4 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 4 +title: 第22篇:HAL GPIO 输入 API —— 怎么用代码读到按钮状态 +description: '' --- # 第22篇:HAL GPIO 输入 API —— 怎么用代码读到按钮状态 diff --git a/documents/vol8-domains/embedded/02-button/05-c-polling-button.md b/documents/vol8-domains/embedded/02-button/05-c-polling-button.md index 2fca24af5..bd3cc4ac1 100644 --- a/documents/vol8-domains/embedded/02-button/05-c-polling-button.md +++ b/documents/vol8-domains/embedded/02-button/05-c-polling-button.md @@ -1,14 +1,15 @@ --- -title: 第23篇:C 语言轮询按钮 —— 第一次亲手让按钮控制 LED -description: '' +chapter: 16 +difficulty: intermediate +order: 5 +platform: stm32f1 +reading_time_minutes: 9 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 5 +title: 第23篇:C 语言轮询按钮 —— 第一次亲手让按钮控制 LED +description: '' --- # 第23篇:C 语言轮询按钮 —— 第一次亲手让按钮控制 LED diff --git a/documents/vol8-domains/embedded/02-button/06-non-blocking-debounce.md b/documents/vol8-domains/embedded/02-button/06-non-blocking-debounce.md index a62bd6b86..ab8ac9746 100644 --- a/documents/vol8-domains/embedded/02-button/06-non-blocking-debounce.md +++ b/documents/vol8-domains/embedded/02-button/06-non-blocking-debounce.md @@ -1,14 +1,15 @@ --- -title: 第24篇:非阻塞消抖 —— 不让 CPU 停下来等 -description: '' +chapter: 16 +difficulty: intermediate +order: 6 +platform: stm32f1 +reading_time_minutes: 8 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 6 +title: 第24篇:非阻塞消抖 —— 不让 CPU 停下来等 +description: '' --- # 第24篇:非阻塞消抖 —— 不让 CPU 停下来等 diff --git a/documents/vol8-domains/embedded/02-button/07-debounce-state-machine.md b/documents/vol8-domains/embedded/02-button/07-debounce-state-machine.md index a3f0f8e15..589b2e1d5 100644 --- a/documents/vol8-domains/embedded/02-button/07-debounce-state-machine.md +++ b/documents/vol8-domains/embedded/02-button/07-debounce-state-machine.md @@ -1,14 +1,15 @@ --- -title: 第25篇:7 状态消抖状态机 —— 本系列的核心 -description: '' +chapter: 16 +difficulty: intermediate +order: 7 +platform: stm32f1 +reading_time_minutes: 9 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 7 +title: 第25篇:7 状态消抖状态机 —— 本系列的核心 +description: '' --- # 第25篇:7 状态消抖状态机 —— 本系列的核心 diff --git a/documents/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md b/documents/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md index 17c9cfa3e..1942df1d9 100644 --- a/documents/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md +++ b/documents/vol8-domains/embedded/02-button/08-cpp-enum-class-button.md @@ -1,14 +1,15 @@ --- -title: 第26篇:`enum class` 重构按钮代码 —— 类型安全的输入 -description: '' +chapter: 16 +difficulty: intermediate +order: 8 +platform: stm32f1 +reading_time_minutes: 4 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 8 +title: 第26篇:`enum class` 重构按钮代码 —— 类型安全的输入 +description: '' --- # 第26篇:`enum class` 重构按钮代码 —— 类型安全的输入 diff --git a/documents/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md b/documents/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md index f44329494..b9cd981bd 100644 --- a/documents/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md +++ b/documents/vol8-domains/embedded/02-button/09-cpp-variant-and-visit.md @@ -1,14 +1,15 @@ --- -title: '第27篇:`std::variant` 事件 + `std::visit` 分发 —— 类型安全的"发生了什么"' -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 16 +difficulty: intermediate order: 9 +platform: stm32f1 +reading_time_minutes: 7 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第27篇:`std::variant` 事件 + `std::visit` 分发 —— 类型安全的"发生了什么" +description: '' --- # 第27篇:`std::variant` 事件 + `std::visit` 分发 —— 类型安全的"发生了什么" diff --git a/documents/vol8-domains/embedded/02-button/10-cpp-template-button.md b/documents/vol8-domains/embedded/02-button/10-cpp-template-button.md index 3888fdbf1..03623645b 100644 --- a/documents/vol8-domains/embedded/02-button/10-cpp-template-button.md +++ b/documents/vol8-domains/embedded/02-button/10-cpp-template-button.md @@ -1,14 +1,15 @@ --- -title: 第28篇:Button 模板类设计 —— 把一切交给编译器 -description: '' +chapter: 16 +difficulty: intermediate +order: 10 +platform: stm32f1 +reading_time_minutes: 6 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 10 +title: 第28篇:Button 模板类设计 —— 把一切交给编译器 +description: '' --- # 第28篇:Button 模板类设计 —— 把一切交给编译器 diff --git a/documents/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md b/documents/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md index afa296eae..9e90cc330 100644 --- a/documents/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md +++ b/documents/vol8-domains/embedded/02-button/11-cpp-concepts-callback.md @@ -1,14 +1,15 @@ --- -title: 第29篇:Concepts 约束回调 + 完整代码走读 -description: '' +chapter: 16 +difficulty: intermediate +order: 11 +platform: stm32f1 +reading_time_minutes: 7 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 11 +title: 第29篇:Concepts 约束回调 + 完整代码走读 +description: '' --- # 第29篇:Concepts 约束回调 + 完整代码走读 diff --git a/documents/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md b/documents/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md index 86dd95025..37e05459e 100644 --- a/documents/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md +++ b/documents/vol8-domains/embedded/02-button/12-exti-interrupt-and-exercises.md @@ -1,14 +1,15 @@ --- -title: 第30篇:EXTI 中断 + 坑位与练习 -description: '' +chapter: 16 +difficulty: intermediate +order: 12 +platform: stm32f1 +reading_time_minutes: 10 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 16 -order: 12 +title: 第30篇:EXTI 中断 + 坑位与练习 +description: '' --- # 第30篇:EXTI 中断 + 坑位与练习 diff --git a/documents/vol8-domains/embedded/02-static-and-stack-allocation.md b/documents/vol8-domains/embedded/02-static-and-stack-allocation.md index 50bd09ee9..6d06854d1 100644 --- a/documents/vol8-domains/embedded/02-static-and-stack-allocation.md +++ b/documents/vol8-domains/embedded/02-static-and-stack-allocation.md @@ -11,7 +11,7 @@ order: 2 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 6 +reading_time_minutes: 5 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/02-type-safe-register-access.md b/documents/vol8-domains/embedded/02-type-safe-register-access.md index 419fbd6a7..5136f5d08 100644 --- a/documents/vol8-domains/embedded/02-type-safe-register-access.md +++ b/documents/vol8-domains/embedded/02-type-safe-register-access.md @@ -11,7 +11,7 @@ order: 2 platform: stm32f1 prerequisites: - 'Chapter 7: 容器与数据结构' -reading_time_minutes: 7 +reading_time_minutes: 4 tags: - cpp-modern - stm32f1 diff --git a/documents/vol8-domains/embedded/03-circular-buffer.md b/documents/vol8-domains/embedded/03-circular-buffer.md index 574c801ea..d11e8f2ed 100644 --- a/documents/vol8-domains/embedded/03-circular-buffer.md +++ b/documents/vol8-domains/embedded/03-circular-buffer.md @@ -11,7 +11,7 @@ order: 3 platform: stm32f1 prerequisites: - 'Chapter 6: RAII与智能指针' -reading_time_minutes: 6 +reading_time_minutes: 4 tags: - cpp-modern - stm32f1 diff --git a/documents/vol8-domains/embedded/03-object-pool-pattern.md b/documents/vol8-domains/embedded/03-object-pool-pattern.md index 424ee4bf3..42664adf9 100644 --- a/documents/vol8-domains/embedded/03-object-pool-pattern.md +++ b/documents/vol8-domains/embedded/03-object-pool-pattern.md @@ -11,7 +11,7 @@ order: 3 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 8 +reading_time_minutes: 5 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/03-uart/01-motivation-and-overview.md b/documents/vol8-domains/embedded/03-uart/01-motivation-and-overview.md index 5302dcb0c..8bbbd3ce4 100644 --- a/documents/vol8-domains/embedded/03-uart/01-motivation-and-overview.md +++ b/documents/vol8-domains/embedded/03-uart/01-motivation-and-overview.md @@ -1,14 +1,15 @@ --- -title: "第31篇:从按钮到串口 —— 为什么 UART 是嵌入式通信的基石" -description: "" -tags: - - beginner - - cpp-modern - - stm32f1 -difficulty: beginner -platform: stm32f1 chapter: 17 +difficulty: beginner order: 1 +platform: stm32f1 +reading_time_minutes: 11 +tags: +- beginner +- cpp-modern +- stm32f1 +title: 第31篇:从按钮到串口 —— 为什么 UART 是嵌入式通信的基石 +description: '' --- # 第31篇:从按钮到串口 —— 为什么 UART 是嵌入式通信的基石 diff --git a/documents/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md b/documents/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md index 40a1baa11..c79d2c27e 100644 --- a/documents/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md +++ b/documents/vol8-domains/embedded/03-uart/02-uart-protocol-basics.md @@ -1,14 +1,15 @@ --- -title: "第32篇:UART 协议详解 —— 没有时钟线怎么同步" -description: "" -tags: - - beginner - - cpp-modern - - stm32f1 -difficulty: beginner -platform: stm32f1 chapter: 17 +difficulty: beginner order: 2 +platform: stm32f1 +reading_time_minutes: 11 +tags: +- beginner +- cpp-modern +- stm32f1 +title: 第32篇:UART 协议详解 —— 没有时钟线怎么同步 +description: '' --- # 第32篇:UART 协议详解 —— 没有时钟线怎么同步 diff --git a/documents/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md b/documents/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md index 3de622e55..ad7ec7305 100644 --- a/documents/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md +++ b/documents/vol8-domains/embedded/03-uart/03-stm32-usart-peripheral.md @@ -1,14 +1,15 @@ --- -title: "第33篇:STM32 USART 外设 —— 芯片内部的串口引擎" -description: "" -tags: - - beginner - - cpp-modern - - stm32f1 -difficulty: beginner -platform: stm32f1 chapter: 17 +difficulty: beginner order: 3 +platform: stm32f1 +reading_time_minutes: 9 +tags: +- beginner +- cpp-modern +- stm32f1 +title: 第33篇:STM32 USART 外设 —— 芯片内部的串口引擎 +description: '' --- # 第33篇:STM32 USART 外设 —— 芯片内部的串口引擎 diff --git a/documents/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md b/documents/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md index fbe7cc9f8..c34a932e8 100644 --- a/documents/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md +++ b/documents/vol8-domains/embedded/03-uart/04-hal-uart-init-and-send.md @@ -1,14 +1,15 @@ --- -title: "第34篇:HAL UART 初始化与发送 —— 让芯片开口说话" -description: "" -tags: - - beginner - - cpp-modern - - stm32f1 -difficulty: beginner -platform: stm32f1 chapter: 17 +difficulty: beginner order: 4 +platform: stm32f1 +reading_time_minutes: 8 +tags: +- beginner +- cpp-modern +- stm32f1 +title: 第34篇:HAL UART 初始化与发送 —— 让芯片开口说话 +description: '' --- # 第34篇:HAL UART 初始化与发送 —— 让芯片开口说话 diff --git a/documents/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md b/documents/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md index 8f6e0336d..1cbf2c27c 100644 --- a/documents/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md +++ b/documents/vol8-domains/embedded/03-uart/05-printf-redirect-and-blocking-receive.md @@ -1,14 +1,15 @@ --- -title: "第35篇:printf 重定向与阻塞接收 —— 让芯片用 printf 说话,也学会倾听" -description: "" -tags: - - beginner - - cpp-modern - - stm32f1 -difficulty: beginner -platform: stm32f1 chapter: 17 +difficulty: beginner order: 5 +platform: stm32f1 +reading_time_minutes: 8 +tags: +- beginner +- cpp-modern +- stm32f1 +title: 第35篇:printf 重定向与阻塞接收 —— 让芯片用 printf 说话,也学会倾听 +description: '' --- # 第35篇:printf 重定向与阻塞接收 —— 让芯片用 printf 说话,也学会倾听 diff --git a/documents/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md b/documents/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md index 64ac69584..dbfc39631 100644 --- a/documents/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md +++ b/documents/vol8-domains/embedded/03-uart/06-interrupt-fundamentals-and-nvic.md @@ -1,14 +1,15 @@ --- -title: "第36篇:中断基础与 NVIC —— 让硬件主动通知 CPU" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 6 +platform: stm32f1 +reading_time_minutes: 9 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第36篇:中断基础与 NVIC —— 让硬件主动通知 CPU +description: '' --- # 第36篇:中断基础与 NVIC —— 让硬件主动通知 CPU diff --git a/documents/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md b/documents/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md index 7b81474e5..4020da6fc 100644 --- a/documents/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md +++ b/documents/vol8-domains/embedded/03-uart/07-circular-buffer-lock-free-spsc.md @@ -1,14 +1,15 @@ --- -title: "第37篇:无锁环形缓冲区 —— ISR 与主循环之间的安全通道" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 7 +platform: stm32f1 +reading_time_minutes: 9 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第37篇:无锁环形缓冲区 —— ISR 与主循环之间的安全通道 +description: '' --- # 第37篇:无锁环形缓冲区 —— ISR 与主循环之间的安全通道 diff --git a/documents/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md b/documents/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md index e3bb7a075..9faad1ab7 100644 --- a/documents/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md +++ b/documents/vol8-domains/embedded/03-uart/08-uart-irq-handler-and-callback.md @@ -1,14 +1,15 @@ --- -title: "第38篇:UART IRQ 处理与回调 —— 中断接收的完整拼图" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 8 +platform: stm32f1 +reading_time_minutes: 8 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第38篇:UART IRQ 处理与回调 —— 中断接收的完整拼图 +description: '' --- # 第38篇:UART IRQ 处理与回调 —— 中断接收的完整拼图 diff --git a/documents/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md b/documents/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md index d40561489..9e7579a2c 100644 --- a/documents/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md +++ b/documents/vol8-domains/embedded/03-uart/09-cpp-expected-and-error-handling.md @@ -1,14 +1,15 @@ --- -title: "第39篇:std::expected 错误处理 —— 嵌入式中比异常更好的选择" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 9 +platform: stm32f1 +reading_time_minutes: 7 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第39篇:std::expected 错误处理 —— 嵌入式中比异常更好的选择 +description: '' --- # 第39篇:std::expected 错误处理 —— 嵌入式中比异常更好的选择 diff --git a/documents/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md b/documents/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md index 9d0db8680..c42abd0b2 100644 --- a/documents/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md +++ b/documents/vol8-domains/embedded/03-uart/10-cpp-uart-driver-template.md @@ -1,14 +1,15 @@ --- -title: "第40篇:UART 驱动模板 —— 零大小抽象与编译时分发" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 10 +platform: stm32f1 +reading_time_minutes: 8 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第40篇:UART 驱动模板 —— 零大小抽象与编译时分发 +description: '' --- # 第40篇:UART 驱动模板 —— 零大小抽象与编译时分发 diff --git a/documents/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md b/documents/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md index d9751fd7c..995732f09 100644 --- a/documents/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md +++ b/documents/vol8-domains/embedded/03-uart/11-cpp-concepts-and-uart-manager.md @@ -1,14 +1,15 @@ --- -title: "第41篇:Concepts 约束 GPIO 初始化 + UartManager —— 类型安全的组装" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 11 +platform: stm32f1 +reading_time_minutes: 7 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第41篇:Concepts 约束 GPIO 初始化 + UartManager —— 类型安全的组装 +description: '' --- # 第41篇:Concepts 约束 GPIO 初始化 + UartManager —— 类型安全的组装 diff --git a/documents/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md b/documents/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md index 2849aec50..bd22b41cc 100644 --- a/documents/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md +++ b/documents/vol8-domains/embedded/03-uart/12-command-processor-and-main-walkthrough.md @@ -1,14 +1,15 @@ --- -title: "第42篇:命令处理器与完整代码走读 —— 从串口输入到 LED 控制" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 12 +platform: stm32f1 +reading_time_minutes: 7 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第42篇:命令处理器与完整代码走读 —— 从串口输入到 LED 控制 +description: '' --- # 第42篇:命令处理器与完整代码走读 —— 从串口输入到 LED 控制 diff --git a/documents/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md b/documents/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md index 63d5477f9..e97fde852 100644 --- a/documents/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md +++ b/documents/vol8-domains/embedded/03-uart/13-pitfalls-and-exercises.md @@ -1,14 +1,15 @@ --- -title: "第43篇:常见坑位与实战练习 —— 把 UART 玩出花样来" -description: "" -tags: - - cpp-modern - - intermediate - - stm32f1 -difficulty: intermediate -platform: stm32f1 chapter: 17 +difficulty: intermediate order: 13 +platform: stm32f1 +reading_time_minutes: 8 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 第43篇:常见坑位与实战练习 —— 把 UART 玩出花样来 +description: '' --- # 第43篇:常见坑位与实战练习 —— 把 UART 玩出花样来 diff --git a/documents/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md b/documents/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md index 0a9adba13..1ac6a2d4b 100644 --- a/documents/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md +++ b/documents/vol8-domains/embedded/04-crtp-vs-runtime-polymorphism.md @@ -11,7 +11,7 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 1: 构建工具链' -reading_time_minutes: 8 +reading_time_minutes: 7 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/04-intrusive-containers.md b/documents/vol8-domains/embedded/04-intrusive-containers.md index 5f3385154..33e84c442 100644 --- a/documents/vol8-domains/embedded/04-intrusive-containers.md +++ b/documents/vol8-domains/embedded/04-intrusive-containers.md @@ -11,7 +11,7 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 6: RAII与智能指针' -reading_time_minutes: 9 +reading_time_minutes: 6 tags: - cpp-modern - stm32f1 diff --git a/documents/vol8-domains/embedded/04-placement-new.md b/documents/vol8-domains/embedded/04-placement-new.md index a561bf31a..4e3b7b44c 100644 --- a/documents/vol8-domains/embedded/04-placement-new.md +++ b/documents/vol8-domains/embedded/04-placement-new.md @@ -11,7 +11,7 @@ order: 4 platform: stm32f1 prerequisites: - 'Chapter 3: 内存与对象管理' -reading_time_minutes: 11 +reading_time_minutes: 8 tags: - cpp-modern - intermediate diff --git a/documents/vol8-domains/embedded/05-interrupt-safe-coding.md b/documents/vol8-domains/embedded/05-interrupt-safe-coding.md index f7f9725b3..78871a894 100644 --- a/documents/vol8-domains/embedded/05-interrupt-safe-coding.md +++ b/documents/vol8-domains/embedded/05-interrupt-safe-coding.md @@ -1,20 +1,23 @@ --- -title: "中断安全的代码编写" -description: "ISR安全编程实践" chapter: 10 -order: 5 -tags: - - cpp-modern - - intermediate - - stm32f1 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: ISR安全编程实践 difficulty: advanced -reading_time_minutes: 25 -prerequisites: - - "Chapter 10.1-10.4: 原子操作与内存序" -cpp_standard: [11, 14, 17, 20] +order: 5 platform: stm32f1 +prerequisites: +- 'Chapter 10.1-10.4: 原子操作与内存序' +reading_time_minutes: 15 +tags: +- cpp-modern +- intermediate +- stm32f1 +title: 中断安全的代码编写 --- - # 嵌入式现代C++开发——中断安全的代码编写 ## 引言 diff --git a/documents/vol8-domains/embedded/core-embedded-cpp-index.md b/documents/vol8-domains/embedded/core-embedded-cpp-index.md index dae8de89d..c17527693 100644 --- a/documents/vol8-domains/embedded/core-embedded-cpp-index.md +++ b/documents/vol8-domains/embedded/core-embedded-cpp-index.md @@ -1,14 +1,15 @@ --- -title: 目录 -description: '' +chapter: 0 +difficulty: intermediate +order: 0 +platform: stm32f1 +reading_time_minutes: 3 tags: - cpp-modern - intermediate - stm32f1 -difficulty: intermediate -platform: stm32f1 -chapter: 0 -order: 0 +title: 目录 +description: '' --- # 目录 @@ -43,9 +44,9 @@ order: 0 ## Chapter 3 - 内存与对象管理 -- [初始化列表](../../vol3-standard-library/01-initializer-lists.md) +- [初始化列表](../../vol3-standard-library/11-initializer-lists.md) - [空基类优化(EBO)](../../vol4-advanced/03-empty-base-optimization.md) -- [对象大小,平凡类型](../../vol3-standard-library/05-object-size-and-trivial-types.md) +- [对象大小,平凡类型](../../vol3-standard-library/12-object-size-and-trivial-types.md) ## Chapter 4 - 编译期计算 @@ -61,12 +62,12 @@ order: 0 ## Chapter 7 - 容器与数据结构 -- [array](../../vol3-standard-library/01-array.md) -- [span](../../vol3-standard-library/02-span.md) +- [array](../../vol3-standard-library/02-array.md) +- [span](../../vol3-standard-library/08-span.md) - [循环缓冲区](./03-circular-buffer.md) - [侵入式容器设计](./04-intrusive-containers.md) - [ETL](./05-etl.md) -- [自定义的分配器](../../vol3-standard-library/06-custom-allocators.md) +- [自定义的分配器](../../vol3-standard-library/13-custom-allocators.md) ## Chapter 8 - 类型安全与工具类型 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md index c54ce828f..24e8fa7d1 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-1-once-callback-motivation-and-api-design.md @@ -1,27 +1,27 @@ --- -title: "OnceCallback 实战(一):动机与接口设计" -description: "从一次真实的异步回调 bug 出发,拆解 std::function 在异步场景的三大缺陷,设计 OnceCallback 的完整目标 API" chapter: 1 -order: 1 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - 函数对象 +cpp_standard: +- 23 +description: 从一次真实的异步回调 bug 出发,拆解 std::function 在异步场景的三大缺陷,设计 OnceCallback 的完整目标 API difficulty: beginner +order: 1 platform: host -cpp_standard: [23] -reading_time_minutes: 11 prerequisites: - - "OnceCallback 前置知识(一):函数类型与模板偏特化" - - "OnceCallback 前置知识(五):std::move_only_function" - - "OnceCallback 前置知识(六):Deducing this" +- OnceCallback 前置知识(一):函数类型与模板偏特化 +- OnceCallback 前置知识(五):std::move_only_function +- OnceCallback 前置知识(六):Deducing this +reading_time_minutes: 10 related: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +title: OnceCallback 实战(一):动机与接口设计 --- - # OnceCallback 实战(一):动机与接口设计 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md index af36c32c8..ea5f5d4bb 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-2-once-callback-core-skeleton.md @@ -1,30 +1,30 @@ --- -title: "OnceCallback 实战(二):核心骨架搭建" -description: "从零开始五步搭建 OnceCallback 的类骨架——模板偏特化、数据成员、构造函数约束、run() 消费语义、查询接口" chapter: 1 -order: 2 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - 函数对象 - - 模板 +cpp_standard: +- 23 +description: 从零开始五步搭建 OnceCallback 的类骨架——模板偏特化、数据成员、构造函数约束、run() 消费语义、查询接口 difficulty: beginner +order: 2 platform: host -cpp_standard: [23] -reading_time_minutes: 13 prerequisites: - - "OnceCallback 实战(一):动机与接口设计" - - "OnceCallback 前置知识(一):函数类型与模板偏特化" - - "OnceCallback 前置知识(四):Concepts 与 requires 约束" - - "OnceCallback 前置知识(五):std::move_only_function" - - "OnceCallback 前置知识(六):Deducing this" +- OnceCallback 实战(一):动机与接口设计 +- OnceCallback 前置知识(一):函数类型与模板偏特化 +- OnceCallback 前置知识(四):Concepts 与 requires 约束 +- OnceCallback 前置知识(五):std::move_only_function +- OnceCallback 前置知识(六):Deducing this +reading_time_minutes: 9 related: - - "OnceCallback 实战(三):bind_once 实现" - - "OnceCallback 实战(四):取消令牌设计" +- OnceCallback 实战(三):bind_once 实现 +- OnceCallback 实战(四):取消令牌设计 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: OnceCallback 实战(二):核心骨架搭建 --- - # OnceCallback 实战(二):核心骨架搭建 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md index f796fc8e3..7d58c2544 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-3-once-callback-bind-once.md @@ -1,27 +1,27 @@ --- -title: "OnceCallback 实战(三):bind_once 实现" -description: "逐行拆解 bind_once 的参数绑定实现——从动机到 lambda 捕获包展开,再到手动展开一个完整的模板实例化例子" chapter: 1 -order: 3 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - 函数对象 - - 模板 +cpp_standard: +- 23 +description: 逐行拆解 bind_once 的参数绑定实现——从动机到 lambda 捕获包展开,再到手动展开一个完整的模板实例化例子 difficulty: beginner +order: 3 platform: host -cpp_standard: [23] -reading_time_minutes: 9 prerequisites: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识(二):std::invoke 与统一调用协议" - - "OnceCallback 前置知识(三):Lambda 高级特性" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识(二):std::invoke 与统一调用协议 +- OnceCallback 前置知识(三):Lambda 高级特性 +reading_time_minutes: 7 related: - - "OnceCallback 实战(四):取消令牌设计" +- OnceCallback 实战(四):取消令牌设计 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: OnceCallback 实战(三):bind_once 实现 --- - # OnceCallback 实战(三):bind_once 实现 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md index 579205d15..0838a55a3 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-4-once-callback-cancellation-token.md @@ -1,28 +1,29 @@ --- -title: "OnceCallback 实战(四):取消令牌设计" -description: "深入理解 CancelableToken 的设计——用 shared_ptr + atomic 实现轻量级取消机制,以及它如何集成到 OnceCallback 的执行流程中" chapter: 1 -order: 4 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - atomic - - 智能指针 - - 引用计数 +cpp_standard: +- 23 +description: 深入理解 CancelableToken 的设计——用 shared_ptr + atomic 实现轻量级取消机制,以及它如何集成到 + OnceCallback 的执行流程中 difficulty: beginner +order: 4 platform: host -cpp_standard: [23] -reading_time_minutes: 9 prerequisites: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - - "OnceCallback 实战(五):then 链式组合" - - "OnceCallback 实战(六):测试与性能对比" +- OnceCallback 实战(五):then 链式组合 +- OnceCallback 实战(六):测试与性能对比 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- atomic +- 智能指针 +- 引用计数 +title: OnceCallback 实战(四):取消令牌设计 --- - # OnceCallback 实战(四):取消令牌设计 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md index f575c6c90..265f3db8a 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-5-once-callback-then-chaining.md @@ -1,27 +1,27 @@ --- -title: "OnceCallback 实战(五):then 链式组合" -description: "逐行拆解 then() 的所有权链设计——从管道思维到 void/非 void 分支处理,理解 OnceCallback 中最精巧的所有权管理" chapter: 1 -order: 5 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - 函数对象 - - 模板 +cpp_standard: +- 23 +description: 逐行拆解 then() 的所有权链设计——从管道思维到 void/非 void 分支处理,理解 OnceCallback 中最精巧的所有权管理 difficulty: beginner +order: 5 platform: host -cpp_standard: [23] -reading_time_minutes: 9 prerequisites: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识(二):std::invoke 与统一调用协议" - - "OnceCallback 前置知识(三):Lambda 高级特性" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识(二):std::invoke 与统一调用协议 +- OnceCallback 前置知识(三):Lambda 高级特性 +reading_time_minutes: 7 related: - - "OnceCallback 实战(六):测试与性能对比" +- OnceCallback 实战(六):测试与性能对比 +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +- 模板 +title: OnceCallback 实战(五):then 链式组合 --- - # OnceCallback 实战(五):then 链式组合 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md index 343d72977..0ff855178 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/01-6-once-callback-testing-and-perf.md @@ -1,27 +1,27 @@ --- -title: "OnceCallback 实战(六):测试与性能对比" -description: "系统化设计六类测试用例验证 OnceCallback 的所有核心行为,对比与 Chromium 原版和标准库方案的性能差异" chapter: 1 -order: 6 -tags: - - host - - cpp-modern - - beginner - - 回调机制 - - 函数对象 +cpp_standard: +- 23 +description: 系统化设计六类测试用例验证 OnceCallback 的所有核心行为,对比与 Chromium 原版和标准库方案的性能差异 difficulty: beginner +order: 6 platform: host -cpp_standard: [23] -reading_time_minutes: 10 prerequisites: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 实战(三):bind_once 实现" - - "OnceCallback 实战(四):取消令牌设计" - - "OnceCallback 实战(五):then 链式组合" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 实战(三):bind_once 实现 +- OnceCallback 实战(四):取消令牌设计 +- OnceCallback 实战(五):then 链式组合 +reading_time_minutes: 8 related: - - "OnceCallback 前置知识(五):std::move_only_function" +- OnceCallback 前置知识(五):std::move_only_function +tags: +- host +- cpp-modern +- beginner +- 回调机制 +- 函数对象 +title: OnceCallback 实战(六):测试与性能对比 --- - # OnceCallback 实战(六):测试与性能对比 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md index 915cedba2..c18c95ce6 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-00-once-callback-cpp-basics-review.md @@ -1,25 +1,28 @@ --- -title: "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" -description: "一篇快速复习 OnceCallback 系列所需的所有 C++ 基础特性——移动语义、完美转发、可变参数模板、智能指针、atomic、lambda、类型特征等,为后续深度学习做好准备" chapter: 0 -order: 0 -tags: - - host - - cpp-modern - - intermediate - - 基础 - - 入门 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 一篇快速复习 OnceCallback 系列所需的所有 C++ 基础特性——移动语义、完美转发、可变参数模板、智能指针、atomic、lambda、类型特征等,为后续深度学习做好准备 difficulty: intermediate +order: 0 platform: host -cpp_standard: [11, 14, 17, 20] -reading_time_minutes: 25 prerequisites: - - "卷一 C++ 基础入门" +- 卷一 C++ 基础入门 +reading_time_minutes: 14 related: - - "OnceCallback 前置知识(一):函数类型与模板偏特化" - - "OnceCallback 前置知识(三):Lambda 高级特性" +- OnceCallback 前置知识(一):函数类型与模板偏特化 +- OnceCallback 前置知识(三):Lambda 高级特性 +tags: +- host +- cpp-modern +- intermediate +- 基础 +- 入门 +title: OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 --- - # OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md index ea6debc47..b4a263e6c 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-01-once-callback-function-type-and-specialization.md @@ -1,25 +1,28 @@ --- -title: "OnceCallback 前置知识(一):函数类型与模板偏特化" -description: "深入理解函数类型 int(int,int) 是什么,以及 OnceCallback 背后的模板偏特化技巧——编译器如何通过模式匹配拆解函数签名" chapter: 0 -order: 1 -tags: - - host - - cpp-modern - - intermediate - - 模板 - - 泛型 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 深入理解函数类型 int(int,int) 是什么,以及 OnceCallback 背后的模板偏特化技巧——编译器如何通过模式匹配拆解函数签名 difficulty: intermediate +order: 1 platform: host -cpp_standard: [11, 14, 17, 20] -reading_time_minutes: 10 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - - "OnceCallback 前置知识(五):std::move_only_function" - - "OnceCallback 实战(二):核心骨架搭建" +- OnceCallback 前置知识(五):std::move_only_function +- OnceCallback 实战(二):核心骨架搭建 +tags: +- host +- cpp-modern +- intermediate +- 模板 +- 泛型 +title: OnceCallback 前置知识(一):函数类型与模板偏特化 --- - # OnceCallback 前置知识(一):函数类型与模板偏特化 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md index ee0365d4c..a05b6d7cb 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-02-once-callback-invoke-and-callable.md @@ -1,26 +1,27 @@ --- -title: "OnceCallback 前置知识(二):std::invoke 与统一调用协议" -description: "深入理解 std::invoke 如何统一函数指针、成员函数指针、lambda、仿函数的调用方式,以及 std::invoke_result_t 在 OnceCallback 中的类型推导作用" chapter: 0 -order: 2 -tags: - - host - - cpp-modern - - intermediate - - 函数对象 - - std_invoke +cpp_standard: +- 17 +description: 深入理解 std::invoke 如何统一函数指针、成员函数指针、lambda、仿函数的调用方式,以及 std::invoke_result_t + 在 OnceCallback 中的类型推导作用 difficulty: intermediate +order: 2 platform: host -cpp_standard: [17] -reading_time_minutes: 10 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" - - "OnceCallback 前置知识(一):函数类型与模板偏特化" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +- OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 8 related: - - "OnceCallback 实战(三):bind_once 实现" - - "OnceCallback 实战(五):then 链式组合" +- OnceCallback 实战(三):bind_once 实现 +- OnceCallback 实战(五):then 链式组合 +tags: +- host +- cpp-modern +- intermediate +- 函数对象 +- std_invoke +title: OnceCallback 前置知识(二):std::invoke 与统一调用协议 --- - # OnceCallback 前置知识(二):std::invoke 与统一调用协议 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md index d4bc35ab8..6a5bafadd 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-03-once-callback-lambda-advanced.md @@ -1,25 +1,29 @@ --- -title: "OnceCallback 前置知识(三):Lambda 高级特性" -description: "深入讲解 mutable lambda、初始化捕获(init capture)、C++20 lambda capture pack expansion 和泛型 lambda——OnceCallback 中 bind_once 与 then() 的核心实现技巧" chapter: 0 -order: 3 -tags: - - host - - cpp-modern - - intermediate - - lambda - - 函数对象 +cpp_standard: +- 14 +- 17 +- 20 +- 23 +description: 深入讲解 mutable lambda、初始化捕获(init capture)、C++20 lambda capture pack expansion + 和泛型 lambda——OnceCallback 中 bind_once 与 then() 的核心实现技巧 difficulty: intermediate +order: 3 platform: host -cpp_standard: [14, 17, 20, 23] -reading_time_minutes: 11 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - - "OnceCallback 实战(三):bind_once 实现" - - "OnceCallback 实战(五):then 链式组合" +- OnceCallback 实战(三):bind_once 实现 +- OnceCallback 实战(五):then 链式组合 +tags: +- host +- cpp-modern +- intermediate +- lambda +- 函数对象 +title: OnceCallback 前置知识(三):Lambda 高级特性 --- - # OnceCallback 前置知识(三):Lambda 高级特性 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md index a1f6986ac..f6d3f3dff 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-04-once-callback-concepts-and-requires.md @@ -1,26 +1,26 @@ --- -title: "OnceCallback 前置知识(四):Concepts 与 requires 约束" -description: "从模板构造函数劫持移动构造函数的真实问题出发,理解 Concepts 和 requires 约束如何保护 OnceCallback 的构造函数正确匹配" chapter: 0 -order: 4 -tags: - - host - - cpp-modern - - intermediate - - concepts - - 模板 +cpp_standard: +- 20 +description: 从模板构造函数劫持移动构造函数的真实问题出发,理解 Concepts 和 requires 约束如何保护 OnceCallback 的构造函数正确匹配 difficulty: intermediate +order: 4 platform: host -cpp_standard: [20] -reading_time_minutes: 11 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" - - "OnceCallback 前置知识(一):函数类型与模板偏特化" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +- OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 9 related: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识(五):std::move_only_function" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识(五):std::move_only_function +tags: +- host +- cpp-modern +- intermediate +- concepts +- 模板 +title: OnceCallback 前置知识(四):Concepts 与 requires 约束 --- - # OnceCallback 前置知识(四):Concepts 与 requires 约束 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md index 4f068bb01..919539f7e 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-05-once-callback-move-only-function.md @@ -1,26 +1,27 @@ --- -title: "OnceCallback 前置知识(五):std::move_only_function (C++23)" -description: "深入理解 C++23 的 std::move_only_function——OnceCallback 的核心存储类型,从 std::function 的演进动机到 SBO 行为,再到为什么 OnceCallback 需要独立的三态管理" chapter: 0 -order: 5 -tags: - - host - - cpp-modern - - intermediate - - 函数对象 - - 智能指针 +cpp_standard: +- 23 +description: 深入理解 C++23 的 std::move_only_function——OnceCallback 的核心存储类型,从 std::function + 的演进动机到 SBO 行为,再到为什么 OnceCallback 需要独立的三态管理 difficulty: intermediate +order: 5 platform: host -cpp_standard: [23] -reading_time_minutes: 10 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" - - "OnceCallback 前置知识(一):函数类型与模板偏特化" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +- OnceCallback 前置知识(一):函数类型与模板偏特化 +reading_time_minutes: 9 related: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 实战(六):测试与性能对比" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 实战(六):测试与性能对比 +tags: +- host +- cpp-modern +- intermediate +- 函数对象 +- 智能指针 +title: OnceCallback 前置知识(五):std::move_only_function (C++23) --- - # OnceCallback 前置知识(五):std::move_only_function (C++23) ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md index 64898bc70..52a49a617 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/full/pre-06-once-callback-deducing-this.md @@ -1,24 +1,25 @@ --- -title: "OnceCallback 前置知识(六):Deducing this (C++23)" -description: "深入理解 C++23 显式对象参数(deducing this)如何让 OnceCallback::run() 在编译期优雅地拦截左值调用,替代 Chromium 的双重重载 hack" chapter: 0 -order: 6 -tags: - - host - - cpp-modern - - intermediate - - 模板 +cpp_standard: +- 23 +description: 深入理解 C++23 显式对象参数(deducing this)如何让 OnceCallback::run() 在编译期优雅地拦截左值调用,替代 + Chromium 的双重重载 hack difficulty: intermediate +order: 6 platform: host -cpp_standard: [23] -reading_time_minutes: 10 prerequisites: - - "OnceCallback 前置知识速查:C++11/14/17 核心特性回顾" +- OnceCallback 前置知识速查:C++11/14/17 核心特性回顾 +reading_time_minutes: 8 related: - - "OnceCallback 实战(二):核心骨架搭建" - - "OnceCallback 前置知识(四):Concepts 与 requires 约束" +- OnceCallback 实战(二):核心骨架搭建 +- OnceCallback 前置知识(四):Concepts 与 requires 约束 +tags: +- host +- cpp-modern +- intermediate +- 模板 +title: OnceCallback 前置知识(六):Deducing this (C++23) --- - # OnceCallback 前置知识(六):Deducing this (C++23) ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md index 353063d7e..60d1c9755 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/01-once-callback-design.md @@ -1,26 +1,27 @@ --- -title: "once_callback 设计指南(一):动机与接口设计" -description: "从 Chromium OnceCallback 出发,设计一个 C++23 的 move-only、一次性消费回调组件——第一部分聚焦动机分析和 API 设计" chapter: 1 -order: 1 -tags: - - host - - cpp-modern - - advanced - - 回调机制 - - 函数对象 +cpp_standard: +- 23 +description: 从 Chromium OnceCallback 出发,设计一个 C++23 的 move-only、一次性消费回调组件——第一部分聚焦动机分析和 + API 设计 difficulty: advanced +order: 1 platform: host -cpp_standard: [23] -reading_time_minutes: 20 prerequisites: - - "std::function、std::invoke 与可调用对象" - - "移动语义与完美转发" +- std::function、std::invoke 与可调用对象 +- 移动语义与完美转发 +reading_time_minutes: 19 related: - - "OnceCallback 与 RepeatingCallback" - - "bind_once / bind_repeating 与参数绑定" +- OnceCallback 与 RepeatingCallback +- bind_once / bind_repeating 与参数绑定 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: once_callback 设计指南(一):动机与接口设计 --- - # once_callback 设计指南(一):动机与接口设计 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md index 699307873..cc7fcfa39 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/02-once-callback-implementation.md @@ -1,25 +1,25 @@ --- -title: "once_callback 设计指南(二):逐步实现" -description: "从核心骨架到完整组件,四步走读 once_callback 的实现策略,重点理解模板技巧和所有权设计" chapter: 1 -order: 2 -tags: - - host - - cpp-modern - - advanced - - 回调机制 - - 函数对象 +cpp_standard: +- 23 +description: 从核心骨架到完整组件,四步走读 once_callback 的实现策略,重点理解模板技巧和所有权设计 difficulty: advanced +order: 2 platform: host -cpp_standard: [23] -reading_time_minutes: 30 prerequisites: - - "once_callback 设计指南(一):动机与接口设计" +- once_callback 设计指南(一):动机与接口设计 +reading_time_minutes: 24 related: - - "bind_once / bind_repeating 与参数绑定" - - "回调取消与组合模式" +- bind_once / bind_repeating 与参数绑定 +- 回调取消与组合模式 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: once_callback 设计指南(二):逐步实现 --- - # once_callback 设计指南(二):逐步实现 ## 引言 diff --git a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md index 76c392f99..2580184d2 100644 --- a/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md +++ b/documents/vol9-open-source-project-learn/chrome/01_once_callback/hands_on/03-once-callback-testing.md @@ -1,25 +1,25 @@ --- -title: "once_callback 设计指南(三):测试策略与性能对比" -description: "系统设计 once_callback 的测试用例,对比与 Chromium 原版和标准库方案的性能差异,总结设计取舍" chapter: 1 -order: 3 -tags: - - host - - cpp-modern - - advanced - - 回调机制 - - 函数对象 +cpp_standard: +- 23 +description: 系统设计 once_callback 的测试用例,对比与 Chromium 原版和标准库方案的性能差异,总结设计取舍 difficulty: advanced +order: 3 platform: host -cpp_standard: [23] -reading_time_minutes: 20 prerequisites: - - "once_callback 设计指南(一):动机与接口设计" - - "once_callback 设计指南(二):逐步实现" +- once_callback 设计指南(一):动机与接口设计 +- once_callback 设计指南(二):逐步实现 +reading_time_minutes: 12 related: - - "回调取消与组合模式" +- 回调取消与组合模式 +tags: +- host +- cpp-modern +- advanced +- 回调机制 +- 函数对象 +title: once_callback 设计指南(三):测试策略与性能对比 --- - # once_callback 设计指南(三):测试策略与性能对比 ## 引言 diff --git a/package.json b/package.json index b29803f93..c7f91b59c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@types/node": "^25.6.2", "markdown-it-mathjax3": "^4.3.2", "mermaid": "^10.9.6", + "shiki": "^2.5.0", "tsx": "^4.21.0", "vitepress": "^1.6.4", "vue": "^3.5.34" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 297962612..126ee1636 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: mermaid: specifier: ^10.9.6 version: 10.9.6 + shiki: + specifier: ^2.5.0 + version: 2.5.0 tsx: specifier: ^4.21.0 version: 4.21.0 diff --git a/scripts/cppref_manifest.json b/scripts/cppref_manifest.json index 03c6e3d57..1470da0da 100644 --- a/scripts/cppref_manifest.json +++ b/scripts/cppref_manifest.json @@ -40,7 +40,7 @@ "order": 1, "difficulty": "beginner", "cpp_standard": [20, 23], - "tutorial_link": "../vol3-standard-library/02-span.md", + "tutorial_link": "../vol3-standard-library/08-span.md", "embedded_rating": "high" }, { @@ -238,7 +238,7 @@ "order": 4, "difficulty": "beginner", "cpp_standard": [11, 14, 17, 20, 23], - "tutorial_link": "../vol3-standard-library/01-array.md", + "tutorial_link": "../vol3-standard-library/02-array.md", "embedded_rating": "high" }, { @@ -249,7 +249,7 @@ "order": 5, "difficulty": "beginner", "cpp_standard": [11, 14, 17, 20, 23], - "tutorial_link": "../vol3-standard-library/01-initializer-lists.md", + "tutorial_link": "../vol3-standard-library/11-initializer-lists.md", "embedded_rating": "high" }, { diff --git a/scripts/validate_frontmatter.py b/scripts/validate_frontmatter.py index 7f94dbdd9..521543596 100755 --- a/scripts/validate_frontmatter.py +++ b/scripts/validate_frontmatter.py @@ -58,7 +58,7 @@ } VALID_DIFFICULTY = {'beginner', 'intermediate', 'advanced'} -VALID_CPP_STANDARDS = {'11', '14', '17', '20', '23'} +VALID_CPP_STANDARDS = {'11', '14', '17', '20', '23', '26'} # Lecture note specific fields (vol10-open-lecture-notes) VALID_CONFERENCES = {'cppcon', 'cppnow', 'meetingpp', 'course', 'blog'} diff --git a/site/.vitepress/theme/components/OnlineCompilerDemo.vue b/site/.vitepress/theme/components/OnlineCompilerDemo.vue index 6e9bb501f..4fde2dcf5 100644 --- a/site/.vitepress/theme/components/OnlineCompilerDemo.vue +++ b/site/.vitepress/theme/components/OnlineCompilerDemo.vue @@ -1,5 +1,5 @@