diff --git a/book/en/src/cpp11/16-generalized-unions.md b/book/en/src/cpp11/16-generalized-unions.md index 27fb0a2..26ef95c 100644 --- a/book/en/src/cpp11/16-generalized-unions.md +++ b/book/en/src/cpp11/16-generalized-unions.md @@ -15,7 +15,7 @@ The size of a union is at least large enough to hold the largest data member. | Book | Video | Code | X | | --- | --- | --- | --- | -| [cppreference-union](https://cppreference.com/w/cpp/language/union.html) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/16-generalized-unions.md) | [Video Explanation]() | [Exercise Code]() | | +| [cppreference-union](https://cppreference.com/w/cpp/language/union.html) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/16-generalized-unions.md) | [Video Explanation]() | [Exercise Code](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/16-generalized-unions-0.cpp) | | **Why introduced?** @@ -127,7 +127,56 @@ int main() { } ``` -## II. Precautions +## II. Real-World Case — Generalized Unions in the STL + +**std::variant Internal Storage — Recursive Generalized Union** +> Using the `std::variant` implementation from the vendored [MSVC STL](https://github.com/mcpp-community/d2mcpp/tree/main/msvc-stl) as an example (source: [`msvc-stl/stl/inc/variant`](https://github.com/mcpp-community/d2mcpp/blob/main/msvc-stl/stl/inc/variant#L343-L399)), `_CONSTEXPR20` / `_STD` are internal macros and can be ignored while reading + +```cpp +// MSVC STL · msvc-stl/stl/inc/variant (abridged) +template +class _Variant_storage_ { +public: + union { + remove_cv_t<_First> _Head; + _Variant_storage<_Rest...> _Tail; + }; + + _CONSTEXPR20 ~_Variant_storage_() { + // The union does not know which member is active; destruction is + // controlled by the outer variant class + } + // ... +}; +``` + +`std::variant` uses a recursive union to hold multiple types in a single block of memory. The non-trivial destructor variant must explicitly define the destructor — this is exactly the key capability of C++11 generalized unions: unions can contain members with non-trivial special member functions, but their lifecycle must be managed manually + +**std::any Small-Object Optimization — Union as Type-Erased Storage** +> `std::any` uses a union to combine small-object storage, heap pointers, and raw byte buffers into the same memory (source: [`msvc-stl/stl/inc/any`](https://github.com/mcpp-community/d2mcpp/blob/main/msvc-stl/stl/inc/any#L362-L376)) + +```cpp +// MSVC STL · msvc-stl/stl/inc/any (abridged) +class any { + struct _Storage_t { + union { + unsigned char _TrivialData[_Any_trivial_space_size]; + _Small_storage_t _SmallStorage; + _Big_storage_t _BigStorage; + }; + uintptr_t _TypeData; + }; + + union { + _Storage_t _Storage{}; + max_align_t _Dummy; + }; +}; +``` + +> Summary: The core storage of both `std::variant` and `std::any` relies on generalized unions. Before C++11, unions could only hold POD types, forcing the standard library to use raw byte buffers + placement new as a workaround. Generalized unions allow code to directly express "one-of-many" memory layout, with an outer wrapper responsible for tracking the active member and managing its lifecycle, making the code easier to maintain + +## III. Precautions **Accessibility** @@ -171,11 +220,21 @@ m.a = 1; double c = m.b; // Error: undefined behavior. ``` -## III. Exercise Code +## IV. Exercise Code + +### Exercise Code Topics -TODO +- 0 - [Union Default Member Initialization](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/en/cpp11/16-generalized-unions-0.cpp) +- 1 - [Union with Non-Trivial Types and Lifecycle Management](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/en/cpp11/16-generalized-unions-1.cpp) +- 2 - [Tagged Discriminated Union — A Simplified std::variant with enum + union](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/en/cpp11/16-generalized-unions-2.cpp) + +### Exercise Auto-Checker Command + +``` +d2x checker generalized-unions +``` -## IV. Other +## V. Other - [Discussion Forum](https://forum.d2learn.org/category/20) - [d2mcpp Tutorial Repository](https://github.com/mcpp-community/d2mcpp) diff --git a/book/src/cpp11/16-generalized-unions.md b/book/src/cpp11/16-generalized-unions.md index 0c771c7..3934262 100644 --- a/book/src/cpp11/16-generalized-unions.md +++ b/book/src/cpp11/16-generalized-unions.md @@ -14,7 +14,7 @@ | Book | Video | Code | X | | --- | --- | --- | --- | -| [cppreference-union](https://cppreference.com/w/cpp/language/union.html) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/16-generalized-unions.md) | [视频解读]() | [练习代码]() | | +| [cppreference-union](https://cppreference.com/w/cpp/language/union.html) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/16-generalized-unions.md) | [视频解读]() | [练习代码](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/16-generalized-unions-0.cpp) | | **为什么引入** @@ -126,7 +126,55 @@ int main() { } ``` -## 二、注意事项 +## 二、真实案例 - STL 中的广义联合体 + +**std::variant 的内部存储 - 递归广义联合体** +> 以仓库内置的 [MSVC STL](https://github.com/mcpp-community/d2mcpp/tree/main/msvc-stl) 中的 `std::variant` 实现为例 (源码: [`msvc-stl/stl/inc/variant`](https://github.com/mcpp-community/d2mcpp/blob/main/msvc-stl/stl/inc/variant#L343-L399)), `_CONSTEXPR20` / `_STD` 是库内部宏, 阅读时可忽略 + +```cpp +// MSVC STL · msvc-stl/stl/inc/variant (有删节) +template +class _Variant_storage_ { +public: + union { + remove_cv_t<_First> _Head; + _Variant_storage<_Rest...> _Tail; + }; + + _CONSTEXPR20 ~_Variant_storage_() { + // 联合体不知道哪个成员活跃, 析构由外层 variant 控制 + } + // ... +}; +``` + +`std::variant` 通过递归联合体在一块内存里容纳多个不同类型, 非平凡析构版本必须显式定义析构函数 — 这正是 C++11 广义联合体的核心能力: 联合体可以包含有非平凡特殊成员函数的成员, 但需要手动管理生命周期 + +**std::any 的小对象优化 - 联合体做类型擦除存储** +> `std::any` 使用联合体将小对象、大对象指针和原始缓冲区合并到同一块内存 (源码: [`msvc-stl/stl/inc/any`](https://github.com/mcpp-community/d2mcpp/blob/main/msvc-stl/stl/inc/any#L362-L376)) + +```cpp +// MSVC STL · msvc-stl/stl/inc/any (有删节) +class any { + struct _Storage_t { + union { + unsigned char _TrivialData[_Any_trivial_space_size]; + _Small_storage_t _SmallStorage; + _Big_storage_t _BigStorage; + }; + uintptr_t _TypeData; + }; + + union { + _Storage_t _Storage{}; + max_align_t _Dummy; + }; +}; +``` + +> 小结: `std::variant`、`std::any` 的核心存储都依赖广义联合体。C++11 之前联合体只能容纳 POD 类型, 标准库不得不用原始字节缓冲区 + placement new 的迂回方案; 广义联合体让代码可以直接表达"多选一"的内存布局, 并由外层封装负责跟踪活跃成员与管理生命周期, 因而更易维护 + +## 三、注意事项 **可访问性** @@ -170,11 +218,21 @@ m.a = 1; double c = m.b; // 错误:未定义行为 ``` -## 三、练习代码 +## 四、练习代码 + +### 练习代码主题 -TODO +- 0 - [联合体默认成员初始化 - 最多一个变体成员可带默认初始化器](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/16-generalized-unions-0.cpp) +- 1 - [联合体包含非平凡类型及生命周期管理 - placement new 构造 / 显式析构](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/16-generalized-unions-1.cpp) +- 2 - [带标签的鉴别联合体 - 用 enum + union 实现简易 variant](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/16-generalized-unions-2.cpp) + +### 练习代码自动检测命令 + +``` +d2x checker generalized-unions +``` -## 四、其他 +## 五、其他 - [交流讨论](https://forum.d2learn.org/category/20) - [d2mcpp教程仓库](https://github.com/mcpp-community/d2mcpp) diff --git a/dslings/cpp11/16-generalized-unions-2.cpp b/dslings/cpp11/16-generalized-unions-2.cpp new file mode 100644 index 0000000..57bd336 --- /dev/null +++ b/dslings/cpp11/16-generalized-unions-2.cpp @@ -0,0 +1,105 @@ +// d2mcpp: https://github.com/mcpp-community/d2mcpp +// license: Apache-2.0 +// file: dslings/cpp11/16-generalized-unions-2.cpp +// +// Exercise/练习: cpp11 | 16 - generalized unions | 带标签的鉴别联合体 +// +// Tips/提示: +// - 使用 enum 标记联合体中哪个成员是活跃的 +// - 根据标签决定构造哪个成员、析构哪个成员 +// - 联合体本身不知道哪个成员活跃, 标签由外层结构体维护 +// +// Docs/文档: +// - https://cppreference.com/w/cpp/language/union.html +// - https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/16-generalized-unions.md +// +// 练习交流讨论: http://forum.d2learn.org/category/20 +// +// Auto-Checker/自动检测命令: +// +// d2x checker generalized-unions +// + +#include +#include +#include + +// 标签类型 — 标记联合体中哪个成员是活跃的 +enum class Tag { + D2X_YOUR_ANSWER, // 整数活跃 + STRING // 字符串活跃 +}; + +// 联合体 — 包含 int 和 std::string, 需手动管理生命周期 +union Data { + int i; + std::string s; + + Data() : i(0) {} + ~Data() {} // 析构由外层根据 tag 决定 +}; + +// 带标签的鉴别联合体 +struct Value { + Tag tag; + Data data; + + // 构造 int + Value(int val) : tag(D2X_YOUR_ANSWER) { + new (&data.i) int(val); + } + + // 构造 string + Value(const std::string& val) : tag(Tag::STRING) { + D2X_YOUR_ANSWER; + } + + // 获取 int 成员 — 仅当 tag == INTEGER 时合法 + int as_int() { + return data.i; + } + + // 获取 string 成员 — 仅当 tag == STRING 时合法 + const std::string& as_string() { + return data.s; + } + + // 析构 — 根据 tag 决定析构哪个成员 + ~Value() { + if (tag == Tag::STRING) { + D2X_YOUR_ANSWER; + } + } +}; + + +int main() { + + // 1. 构造 int 值并验证 + Value v1(42); + d2x_assert(v1.tag == Tag::INTEGER); + d2x_assert_eq(v1.as_int(), 42); + + // 2. 构造 string 值并验证 + Value v2(std::string("hello")); + d2x_assert(v2.tag == Tag::STRING); + d2x_assert(v2.as_string() == "hello"); + + // 3. 从 string 切换到 int + { + Value v3(std::string("world")); + d2x_assert(v3.as_string() == "world"); + + // 手动析构 string 成员, 切换到 int + v3.data.s.~basic_string(); + v3.tag = Tag::INTEGER; + v3.data.i = 100; + + d2x_assert_eq(v3.as_int(), D2X_YOUR_ANSWER); + + // 离开作用域时 ~Value() 发现 tag == INTEGER, 不析构 string + } + D2X_WAIT + + return 0; +} diff --git a/dslings/cpp11/xmake.lua b/dslings/cpp11/xmake.lua index 3f7d247..e7dbdae 100644 --- a/dslings/cpp11/xmake.lua +++ b/dslings/cpp11/xmake.lua @@ -180,6 +180,9 @@ target("cpp11-16-generalized-unions-0") target("cpp11-16-generalized-unions-1") add_files("16-generalized-unions-1.cpp") +target("cpp11-16-generalized-unions-2") + add_files("16-generalized-unions-2.cpp") + -- target: cpp11-17-pod-type target("cpp11-17-pod-type-0") diff --git a/dslings/en/cpp11/16-generalized-unions-2.cpp b/dslings/en/cpp11/16-generalized-unions-2.cpp new file mode 100644 index 0000000..5cd28dc --- /dev/null +++ b/dslings/en/cpp11/16-generalized-unions-2.cpp @@ -0,0 +1,105 @@ +// d2mcpp: https://github.com/mcpp-community/d2mcpp +// license: Apache-2.0 +// file: dslings/en/cpp11/16-generalized-unions-2.cpp +// +// Exercise: cpp11 | 16 - generalized unions | tagged discriminated union +// +// Tips: +// - Use an enum to tag which member of the union is active +// - Choose constructor/destructor based on the tag +// - The union itself cannot track which member is active; the tag is maintained externally +// +// Docs: +// - https://cppreference.com/w/cpp/language/union.html +// - https://github.com/mcpp-community/d2mcpp/blob/main/book/en/src/cpp11/16-generalized-unions.md +// +// Discussion Forum: http://forum.d2learn.org/category/20 +// +// Auto-Checker: +// +// d2x checker generalized-unions +// + +#include +#include +#include + +// Tag type — marks which member of the union is active +enum class Tag { + D2X_YOUR_ANSWER,// integer is active + STRING // string is active +}; + +// Union — contains int and std::string, lifecycle managed externally +union Data { + int i; + std::string s; + + Data() : i(0) {} + ~Data() {} // destruction decided by outer struct based on tag +}; + +// Tagged discriminated union +struct Value { + Tag tag; + Data data; + + // Construct with int + Value(int val) : tag(D2X_YOUR_ANSWER) { + new (&data.i) int(val); + } + + // Construct with string + Value(const std::string& val) : tag(Tag::STRING) { + D2X_YOUR_ANSWER; + } + + // Access int member — valid only when tag == INTEGER + int as_int() { + return data.i; + } + + // Access string member — valid only when tag == STRING + const std::string& as_string() { + return data.s; + } + + // Destructor — destroy the active member based on tag + ~Value() { + if (tag == Tag::STRING) { + D2X_YOUR_ANSWER; + } + } +}; + + +int main() { + + // 1. Construct an int value and verify + Value v1(42); + d2x_assert(v1.tag == Tag::INTEGER); + d2x_assert_eq(v1.as_int(), 42); + + // 2. Construct a string value and verify + Value v2(std::string("hello")); + d2x_assert(v2.tag == Tag::STRING); + d2x_assert(v2.as_string() == "hello"); + + // 3. Switch from string to int + { + Value v3(std::string("world")); + d2x_assert(v3.as_string() == "world"); + + // Manually destroy the string member, then switch to int + v3.data.s.~basic_string(); + v3.tag = Tag::INTEGER; + v3.data.i = 100; + + d2x_assert_eq(v3.as_int(), D2X_YOUR_ANSWER); + + // When v3 goes out of scope, ~Value() sees tag == INTEGER, skips string destruction + } + D2X_WAIT + + return 0; +} diff --git a/dslings/en/cpp11/xmake.lua b/dslings/en/cpp11/xmake.lua index 3f60434..6daeb9e 100644 --- a/dslings/en/cpp11/xmake.lua +++ b/dslings/en/cpp11/xmake.lua @@ -177,6 +177,9 @@ target("cpp11-16-generalized-unions-0") target("cpp11-16-generalized-unions-1") add_files("16-generalized-unions-1.cpp") +target("cpp11-16-generalized-unions-2") + add_files("16-generalized-unions-2.cpp") + -- target: cpp11-17-pod-type target("cpp11-17-pod-type-0") diff --git a/solutions/cpp11/16-generalized-unions-2.cpp b/solutions/cpp11/16-generalized-unions-2.cpp new file mode 100644 index 0000000..4f4a067 --- /dev/null +++ b/solutions/cpp11/16-generalized-unions-2.cpp @@ -0,0 +1,82 @@ +// d2mcpp: https://github.com/mcpp-community/d2mcpp +// license: Apache-2.0 +// reference solution for: dslings/cpp11/16-generalized-unions-2.cpp +// +// 用途: 仅给 CI 与维护者参考使用,不是教程入口。 +// 教程练习入口: dslings/cpp11/16-generalized-unions-2.cpp +// + +#include +#include +#include + +enum class Tag { + INTEGER, + STRING +}; + +union Data { + int i; + std::string s; + + Data() : i(0) {} + ~Data() {} +}; + +struct Value { + Tag tag; + Data data; + + Value(int val) : tag(Tag::INTEGER) { + new (&data.i) int(val); + } + + Value(const std::string& val) : tag(Tag::STRING) { + new (&data.s) std::string(val); + } + + int as_int() { + return data.i; + } + + const std::string& as_string() { + return data.s; + } + + ~Value() { + if (tag == Tag::STRING) { + data.s.~basic_string(); + } + } +}; + + +int main() { + + // 1. 构造 int 值并验证 + Value v1(42); + d2x_assert(v1.tag == Tag::INTEGER); + d2x_assert_eq(v1.as_int(), 42); + + // 2. 构造 string 值并验证 + Value v2(std::string("hello")); + d2x_assert(v2.tag == Tag::STRING); + d2x_assert(v2.as_string() == "hello"); + + // 3. 从 string 切换到 int + { + Value v3(std::string("world")); + d2x_assert(v3.as_string() == "world"); + + // 手动析构 string 成员, 切换到 int + v3.data.s.~basic_string(); + v3.tag = Tag::INTEGER; + v3.data.i = 100; + + d2x_assert_eq(v3.as_int(), 100); + + // 离开作用域时 ~Value() 发现 tag == INTEGER, 不析构 string + } + + return 0; +} diff --git a/solutions/cpp11/xmake.lua b/solutions/cpp11/xmake.lua index 6cfcfce..31cdd51 100644 --- a/solutions/cpp11/xmake.lua +++ b/solutions/cpp11/xmake.lua @@ -183,6 +183,9 @@ target("cpp11-16-generalized-unions-0-ref") target("cpp11-16-generalized-unions-1-ref") add_files("16-generalized-unions-1.cpp") +target("cpp11-16-generalized-unions-2-ref") + add_files("16-generalized-unions-2.cpp") + -- target: cpp11-17-pod-type target("cpp11-17-pod-type-0-ref")