union本质是内存重叠,所有成员共享同一地址且不自动记录活跃类型,需手动管理生命周期与类型标识,否则引发未定义行为;推荐用std::variant替代以获类型安全。

union 在 C++ 中本质是内存重叠,不是类型安全的“多选一”
union 的所有成员共享同一块内存地址,sizeof 取的是最大成员的大小,但**不会自动记录当前实际存的是哪个类型**。这意味着你必须自己维护“活跃成员”的状态,否则读错类型就是未定义行为(UB)。比如写入 int 后读成 float,结果完全不可预测——这不是 bug,是设计如此。
常见错误现象:union 成员初始化后值异常、调试时看到“幻数”、跨平台行为不一致(尤其涉及浮点/整数位模式时)。
- 不能有非 trivial 构造函数、析构函数或拷贝赋值运算符的成员(C++11 起可含
std::string等,但需手动管理生命周期) - 推荐搭配
enum class手动标记当前有效字段,例如:enum class Type { INT, FLOAT, PTR }; - 若需类型安全,优先考虑
std::variant(C++17),它内部用 union + tag 实现,但帮你管住了读写逻辑
用 union 节省内存的关键:只在明确控制生命周期的场景下出手
节省内存不是靠“用了 union 就省”,而是靠**避免为多个互斥状态预留独立空间**。典型适用场景:解析二进制协议、硬件寄存器映射、实现紧凑的 AST 节点、自定义容器中的小对象优化(SOO)。
例如网络包头中,一个字段可能是 IPv4 地址(uint32_t)或 IPv6 地址(uint8_t[16]),二者永不同时存在:
立即学习“C++免费学习笔记(深入)”;
union Addr {
uint32_t v4;
uint8_t v6[16];
};
这里 sizeof(Addr) 是 16,而非 sizeof(uint32_t) + sizeof(uint8_t[16]) 的 20。
- 别为了“看起来省”而把本该独立存在的字段塞进 union —— 比如一个类既有
name又有id,它们逻辑上共存,就绝不该放 union 里 - 注意对齐:union 的对齐取各成员对齐要求的最大值,可能因 padding 导致实际省得没预期多
- 嵌套 union 可进一步压缩,但会显著增加维护成本,慎用
union 和 std::bit_cast / memcpy 的配合才是底层可控的关键
当你需要把一块内存按不同类型解释(比如把 float 的 bit pattern 当作 int 来比较符号位),union 曾是常用手段,但 C++20 前它触发 strict aliasing 规则,属于未定义行为。现在更安全的做法是用 std::bit_cast(C++20)或 memcpy 到 char 数组再 reinterpret_cast。
例如安全地提取 float 的符号位:
int sign_bit(float f) {
auto bits = std::bit_cast(f);
return (bits & 0x80000000) ? -1 : 1;
}
- 旧式 union “类型双关”(type punning)写法:
union { float f; uint32_t i; } u = {.f = f}; return u.i;—— 在 GCC/Clang 上常能工作,但标准不保,且 -O2 可能被优化掉 -
std::memcpy方案兼容所有标准版本,且编译器通常能内联优化,比 union 更可靠 - 别用
reinterpret_cast—— 这是严格禁止的 aliasing 操作(f)
union 成员的构造与析构必须显式管理
C++11 允许 union 包含类类型成员(如 std::string),但**编译器不会帮你调用构造/析构函数**。你得用 placement new 和显式析构,否则资源泄漏或崩溃几乎必然发生。
例如这个 union:
union Payload {
int i;
std::string s;
Payload() : i(0) {} // 必须提供默认构造,且只能初始化 trivial 成员
~Payload() {} // 析构函数必须为空,否则编译报错
};
要存 string,必须手动构造:
Payload p;
new (&p.s) std::string("hello"); // placement new
// ... 使用 p.s ...
p.s.~basic_string(); // 显式析构
底层细节容易被忽略的一点:union 的地址和其任意成员的地址相同,但**成员的偏移量始终为 0**。这意味着你不能指望通过指针算术去“跳转”到下一个成员——它根本不存在。所有操作都必须基于你确切知道当前活跃的是谁,以及你是否已正确管理了它的生命周期。










