C++ union 不能直接包含非平凡类型(如std::string),否则编译报错;必须手动管理生命周期或改用std::variant。

union 在 C++ 里怎么写才不崩
直接说结论:C++ 的 union 不能随便放有非平凡构造/析构函数的类型,否则编译器会报错,比如 error: field '<name>' has implicitly-deleted default constructor</name>。这不是语法写错了,是语义越界了。
根本原因在于:标准规定,union 的每个成员共享同一块内存,编译器无法自动判断该调用谁的构造函数或析构函数——它干脆禁止你放那些“需要被管理”的类型(如 std::string、std::vector、带自定义构造函数的类)。
- 能放心放的:基本类型(
int、double)、POD 结构体(无构造函数、无虚函数、无私有/保护非静态成员)、std::array(如果元素是 POD) - 不能直接放的:
std::string、std::vector、任何含非默认构造函数/析构函数的类 - 想放?得手动管理生命周期:用
placement new构造,显式调用析构函数,且必须严格配对
union 和 class 混合用时,哪些成员能共存
把 union 塞进 class 是常见做法,用来实现“单值多态”(比如一个配置项可能是整数、浮点或布尔)。但关键限制没变:只要 union 本身含非平凡类型,整个 class 就会失去默认构造/拷贝/移动等函数。
这意味着:一旦你在 class 里定义了一个含 std::string 的 union,这个 class 就不再有隐式生成的 class::class(),你必须自己写构造函数,还得负责初始化 union 中的活跃成员。
立即学习“C++免费学习笔记(深入)”;
-
union成员必须显式初始化(哪怕只初始化第一个字段),否则未定义行为 -
class的拷贝构造函数不能依赖默认行为,要手动处理union当前活跃成员的复制逻辑 - 推荐加一个
enum class type_tag成员,和union并列,用于记录当前哪个字段有效(否则运行时无法安全读取)
替代方案:std::variant 比 union 更省心
如果你用的是 C++17 或更新标准,std::variant 几乎就是为解决传统 union 的痛点而生的:它支持非平凡类型、自动管理生命周期、提供 std::visit 安全访问、还能检测访问错误(std::get 抛 std::bad_variant_access)。
代价是:体积略大(多几个字节存储 tag)、访问稍慢(间接跳转)、不能像 union 那样做位级重解释(比如把 float 当 uint32_t 读)。
- 适合场景:需要在几种不同类型间切换,且关注安全性和可维护性
- 不适合场景:嵌入式低延迟、内存极度敏感、或必须做类型 punning(如 IEEE 浮点位操作)
- 示例:
std::variant<int double std::string></int>可直接构造、拷贝、移动,无需手动管理
union 内存布局和对齐陷阱
union 大小等于其最大成员的大小,但对齐要求取所有成员对齐要求的最大值。这容易导致“看起来够用,实际越界”——尤其当结构体成员顺序没排好时。
比如:一个 union { char a; long long b; } 占 8 字节、按 8 对齐;但如果把它嵌进一个 class,而该 class 前面有个 char 成员,那整个 union 的起始地址可能因填充而偏移,影响指针强制转换的可靠性。
- 永远用
sizeof和alignof实际验证,别靠直觉 - 避免跨平台假设:不同编译器/架构下,
long、size_t大小可能不同,影响union布局 - 做类型 punning(如
reinterpret_cast到union)时,确保目标类型满足 strict aliasing 规则,否则优化器可能生成错误代码










