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