Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 64 additions & 5 deletions book/en/src/cpp11/16-generalized-unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?**

Expand Down Expand Up @@ -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 _First, class... _Rest>
class _Variant_storage_<false, _First, _Rest...> {
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**

Expand Down Expand Up @@ -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)
Expand Down
68 changes: 63 additions & 5 deletions book/src/cpp11/16-generalized-unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | |

**为什么引入**

Expand Down Expand Up @@ -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 _First, class... _Rest>
class _Variant_storage_<false, _First, _Rest...> {
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 的迂回方案; 广义联合体让代码可以直接表达"多选一"的内存布局, 并由外层封装负责跟踪活跃成员与管理生命周期, 因而更易维护

## 三、注意事项

**可访问性**

Expand Down Expand Up @@ -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)
Expand Down
105 changes: 105 additions & 0 deletions dslings/cpp11/16-generalized-unions-2.cpp
Original file line number Diff line number Diff line change
@@ -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 <d2x/cpp/common.hpp>
#include <string>
#include <new>

// 标签类型 — 标记联合体中哪个成员是活跃的
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;
}
3 changes: 3 additions & 0 deletions dslings/cpp11/xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading
Loading