普通union不能放有构造函数的类,因C++98/03要求成员必须是平凡可复制类型;C++11起支持非受限联合体,但需手动管理生命周期,推荐优先使用std::variant。

为什么普通 union 不能放有构造函数的类
因为 C++98/03 的 union 要求所有成员必须是“平凡可复制”(trivially copyable)类型,而一旦类定义了构造函数、析构函数、拷贝/移动操作,它就不再是平凡类型——编译器无法确定该调用哪个成员的构造函数,也无法安全决定何时调用析构函数。
现象就是:直接写 union { std::string s; int i; }; 会触发类似 error: field 's' has non-trivial default constructor 的编译错误。
C++11 起怎么让 union 存 class 对象
启用“非受限联合体”(unrestricted union),核心是让编译器允许非平凡类型作为 union 成员,但**不自动管理生命周期**——你得自己显式调用构造、析构、拷贝等操作。
必须满足:
- 类必须是 trivially destructible,或者你手动确保析构被调用(否则资源泄漏)
- union 本身不能有默认构造函数、拷贝/移动操作——这些都得你自己实现
- 推荐用
std::variant替代(C++17),它封装了这些细节;但若必须用 union(比如嵌入式、零开销抽象),就得手动控制
struct NonTrivial {
std::string s;
NonTrivial() : s("hello") {}
~NonTrivial() {} // 注意:这里没做任何事,但已让它 non-trivial
};
union U {
int i;
NonTrivial nt; // C++11 起合法,但不自动调用 NonTrivial::NonTrivial()
U() : i(42) {} // 必须提供构造函数,并明确初始化一个分支
~U() { /* 必须手动析构 nt,如果它被构造过 */ }
};手动管理 union 中对象生命周期的关键点
union 不知道当前哪个成员“活跃”,所以你必须用额外状态(比如枚举)记录,并在切换时:显式调用旧成员的析构、新成员的构造。
常见错误:
- 忘记调用
nt.~NonTrivial()导致std::string内存泄漏 - 用
placement new构造新对象前,没确保旧对象已析构(UB) - 拷贝 union 时只 memcpy 字节,跳过构造逻辑(结果是未定义行为)
- 把
std::vector这类有内部指针的类型放进 union,又没正确处理其移动语义
U u; new (&u.nt) NonTrivial(); // placement new 构造 // ... 使用 u.nt ... u.nt.~NonTrivial(); // 必须显式析构
什么时候该放弃 union 改用 std::variant
如果你需要:安全的值切换、自动析构/移动、支持访问(std::get)、异常安全、或只是不想手写一堆样板代码——直接用 std::variant。
它本质是带 tag 的 union + RAII 封装,底层仍可能用 union 实现,但你不用碰裸指针和 placement new。
限制:
- C++17 起可用;C++14 及更早只能手写或用第三方库(如 Boost.Variant)
- 比裸 union 多几个字节(存 type index),但绝大多数场景可忽略
- 不支持
constexpr构造(直到 C++20 才部分支持)
std::variant 安全可靠;union { int i; std::string s; } 灵活但易出错。真正难的不是语法,而是始终同步“union 当前持有什么类型”和“内存里实际构造了什么”——差一步,就是崩溃或泄漏。









