union能节省内存、实现同一内存块的多类型轮流使用,适用于协议解析等场景;不能自动管理类型状态和非平凡类型生命周期,易致未定义行为,不适用于需类型安全或自动析构的业务逻辑。

union在C++里能干什么,不能干什么
union不是结构体的替代品,它只解决一个核心问题:让同一块内存被不同类型的变量轮流使用,节省空间。但它不自动管理类型切换,也不记录当前存的是哪种类型——这点必须自己处理,否则读错类型会得到未定义行为。
常见错误现象:union成员赋值后,用另一个类型读取却没意识到底层二进制没变;或者把std::string这类有构造/析构函数的类型直接塞进传统union,导致对象生命周期失控。
- 适用场景:底层协议解析(比如网络包中4字节可能表示int或float)、硬件寄存器映射、高性能数值计算中的类型重解释
- 不适用场景:需要同时保存多个字段、需要自动调用构造/析构、类型安全要求高的业务逻辑
- 注意:union的大小等于其最大成员的大小,对齐按最严格成员对齐
怎么安全地用union存std::string或vector
C++11起支持带非平凡类型(即有构造/析构/拷贝函数)的union,但必须手动管理生命周期。不能直接写std::string s;,得用std::string* ptr;或改用std::variant(更推荐)。
如果坚持用union,典型做法是:
立即学习“C++免费学习笔记(深入)”;
- 用
char buffer[sizeof(std::string)]做原始存储 - 赋值时用
new (buffer) std::string("hello")显式构造 - 读取前确认当前状态,读完后用
reinterpret_cast<:string>(buffer)->~string()</:string>显式析构 - 必须配套维护一个枚举或标志位记录当前激活成员,否则无法判断该调哪个析构函数
示例片段:
union U {
int i;
double d;
char str_buf[sizeof(std::string)];
<pre class="brush:php;toolbar:false;">U() : i(0) {}
~U() { /* 必须手动析构激活成员 */ }};
union和std::variant哪个该选
绝大多数新代码应该优先选std::variant。它内部用union加类型标签实现,但封装了所有生命周期管理和访问逻辑,还提供std::visit安全分发。
只有两种情况考虑手写union:
- 目标平台不支持C++17(
std::variant是C++17特性) - 对内存布局和性能有极致要求,且能确保类型切换逻辑绝对正确(比如嵌入式驱动)
错误倾向:看到“节省内存”就下意识用union,结果花更多时间调试类型误读——std::variant多几个字节开销,换来的是编译期检查和运行时安全。
union里的static成员和constexpr怎么用
union可以有static成员,但不能有引用、虚函数、非平凡默认构造函数。C++11起支持constexpr构造函数,但前提是所有成员都满足常量表达式约束。
-
static变量属于整个union类型,不随实例变化,可用作共享状态或计数器 -
constexpr union只能包含POD类型(如int、double),且初始化必须是常量表达式 - 别试图在constexpr union里放
std::array或std::initializer_list——它们不是字面量类型
示例:
constexpr union U {
int i;
float f;
constexpr U(int x) : i(x) {}
};
constexpr U u{42}; // 合法实际项目里最容易被忽略的,是union没有类型记忆能力——它不会告诉你现在里面存的是i还是f,这个责任完全落在程序员肩上。哪怕只多加一个enum class tag字段,也比靠注释或约定靠谱得多。


















