union是同一块内存按不同类型解读的裸金属操作,不管理生命周期、不调用构造/析构函数,仅支持平凡可复制类型,现代c++中主要用作零开销类型双关或底层内存结构封装。

Union 是什么:内存复用的裸金属操作
Union 不是“多个类型可选”,而是“同一块内存按不同方式解读”。它不管理生命周期,不调用构造/析构函数,C++11 之前连 std::string 这类类型都不能放进去——不是语法报错,而是行为未定义。
- 所有成员共享起始地址,
sizeof(union)等于最大成员对齐后大小 - 写入
member_a后读member_b,结果取决于位模式和平台 ABI(比如 float 写入、int 读出,得到的是 IEEE754 位解释) - 没有隐式类型安全:编译器不会阻止你读刚写入的另一个成员,但 C++17 起若类型不满足“可平凡复制”或“布局兼容”,就是未定义行为
现代 C++ 中还能用 Union 做什么
真正还在用的场景非常窄,基本只剩两类:需要零开销的类型双关(type punning),或封装手动内存管理的底层结构(如自定义 variant、序列化缓冲区头)。
-
std::bit_cast(C++20)已替代大部分union类型双关需求,更安全、更明确,且不依赖 union 的“活跃成员”规则 - 嵌入式或协议解析中,仍有人用 union 对齐固定字节布局,例如:
union Header { uint32_t raw; struct { uint8_t a, b, c, d; }; };但要注意:这种用法依赖#pragma pack或alignas控制填充,否则结构体内存布局不可控 - 手写
variant实现时,union 是存储区基础,但必须配合 tag 字段 + 手动调用std::construct_at/std::destroy_at管理对象生命周期
为什么别直接在 Union 里放 std::string 或 vector
因为它们有非平凡构造函数、析构函数和拷贝语义。Union 不会自动调用这些函数,一旦你写入一个 std::string 又覆盖写入 int,前者资源(堆内存)就泄漏了;若之后再读 string,会尝试析构一个未构造的对象,触发崩溃。
- C++11 起,只有“平凡可复制”(trivially copyable)类型才能无条件放进 union;
std::string不满足该要求 - 即使你用 placement new 手动构造,也必须严格保证:每次只激活一个成员,且在切换前显式析构旧成员
- 错误示例:
union U { std::string s; int i; }; U u; u.s = "hello"; u.i = 42; // ❌ s 的析构没被调用,内存泄漏
替代方案比硬啃 Union 更靠谱
95% 的原意想用 union 的场合,其实要的是“运行时单值多类型”——那直接用 std::variant;想绕过类型系统做位操作,优先用 std::bit_cast 或 memcpy;真要极致控制内存,也建议封装成类,把 union 包在里面并严格约束接口。
立即学习“C++免费学习笔记(深入)”;
-
std::variant自动管理生命周期、支持访问者模式、能静态检查是否覆盖所有 case - 用
reinterpret_cast+char*指针做类型双关?不行,违反 strict aliasing 规则;memcpy到临时 buffer 是合法 fallback - union 本身不能被继承、不能有虚函数、不能作为基类——它就是一个纯数据容器,连
= default的拷贝赋值都要你自己写
真正难的从来不是 union 怎么写,而是判断“此刻是否真的需要绕过类型系统”。多数时候,答案是否定的。









