C++中的非受限联合体(Unrestricted Union)是什么?(如何在union中存放类对象)

2次阅读

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

C++中的非受限联合体(Unrestricted Union)是什么?(如何在union中存放类对象)

为什么普通 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 当前持有什么类型”和“内存里实际构造了什么”——差一步,就是崩溃或泄漏。

text=ZqhQzanResources