C++ union 的一种妙用法

目录

1.背景

2.核心作用与设计意图

3.关键设计

4.适用场景与代码示例

5.潜在风险与限制

6.注意事项


1.背景

union,人皆知而鲜用的一个类。我最近在阅读C++标准库的源码时,看到了如下实现:

cpp 复制代码
template<typename T>
struct constant_init {
    union {
        T obj;
    };
    constexpr constant_init() : obj() {}

    ~constant_init() { /* do nothing, union object is not destroyed */}
};

constant_init 用来在编译期创建一个类型 T 的对象 obj,obj 就放在了匿名的 union 中。为何要把一个对象放在 union 中?只因不想让该对象被销毁。

union 便有此用,标准描述为:

Absent default member initializers, if any non-static data member of a union has a non-trivial default constructor, copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor, the corresponding member function of the union will be implicitly deleted unless it is user-provided.

在没有默认成员初始化器的情况下,如果 union 的任一非静态数据成员具有非平凡的默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运行符、移动赋值运算符或析构函数,则其相应的成员函数将被隐式删除(除非用户显式定义)。

例如:

cpp 复制代码
union S {
    int a;
    std::string str;
};

S 无法使用,因为 std::string 导致它的对应成员函数被隐式删除了。

可以通过以下方式来解决。

1.使用 default member initializer

cpp 复制代码
union S {
    int a;
    std::string str = "union str"; // default member initializer
    ~S() {} // explicitly define a destructor
} s;

std::cout << s.str;

default member initializer 会构造一个 std::string 对象,于是成员 str 处于激活状态。此时,S 的默认构造函数不会被删除,但析构函数依旧被移除,需要显式写出(但 std::string 依旧不会被析构,必须显式调用析构)。

2.聚合初始化

cpp 复制代码
union S {
    std::string str;
    int a;
    ~S() {} // explicitly define a destructor
} s = { "union str"s };

std::cout << s.str;

此时,S 的构造函数也被删除了。但是,聚合初始化能够初始化构造函数被删除的对象,聚合初始化的类本身就不允许有构造函数。

需要注意,union 的聚合初始化,默认初始化第一个成员,所以 str 被挪到最前面了。

3.显式写出被删除的函数

这个就不必给例子了。

2.核心作用与设计意图

该模板结构体是 C++ 编译期常量初始化的包装器 ,核心目标是让任意类型 T 支持 constexpr(编译期)默认初始化,同时通过 union 特性避免自动调用 T 的析构函数,适用于需编译期构造且手动管理生命周期的场景。

3.关键设计

  • union 的妙用 :仅包含单个成员 T obj,利用 union 的特性 ------ 编译器不会自动调用成员的析构函数(因 union 无法确定活跃成员),因此析构函数 ~constant_init() 空实现是合理的,避免误调用 obj 的析构。
  • constexpr 构造函数 :C++11 及以上支持 constexpr 构造函数,obj()T 的值初始化(零初始化 + 默认初始化),确保编译期可构造(需 T 的默认构造满足 constexpr 要求或为平凡构造)。
  • 内存布局 :因 union 仅一个成员,constant_init<T> 的大小、对齐方式与 T 完全一致,无额外内存开销。

4.适用场景与代码示例

1.适用条件(必须同时满足)

  • T平凡析构类型std::is_trivially_destructible_v<T>true),否则会导致内存泄漏(obj 的析构未被调用)。
  • T 需支持编译期默认初始化(要么是内置类型 / POD,要么自定义 constexpr T())。

2.典型用法

cpp 复制代码
#include <type_traits>

// 原结构体(添加静态断言增强安全性)
template<typename T>
struct constant_init {
    static_assert(std::is_trivially_destructible_v<T>, 
                  "T must be trivially destructible to avoid memory leak");
    union {
        T obj;
    };
    // C++14+ 支持 constexpr 构造函数初始化列表
    constexpr constant_init() : obj() {}
    // 禁止隐式析构 union 成员
    ~constant_init() {}
};

// 示例1:内置类型(编译期初始化)
constexpr constant_init<int> ci_int; // ci_int.obj = 0(零初始化)
static_assert(ci_int.obj == 0, "compile-time check");

// 示例2:自定义 POD 类型
struct POD { int x; double y; };
constexpr constant_init<POD> ci_pod; // ci_pod.obj.x=0, ci_pod.obj.y=0.0

// 示例3:constexpr 自定义类型
struct ConstExprType {
    constexpr ConstExprType() : val(42) {}
    int val;
};
constexpr constant_init<ConstExprType> ci_cexpr;
static_assert(ci_cexpr.obj.val == 42, "compile-time check");

5.潜在风险与限制

1.非平凡析构类型禁用

T 是非平凡析构(如 std::stringstd::vector),使用该结构体将导致内存泄漏(obj 的析构函数未被调用):

cpp 复制代码
// 错误示例:std::string 是非平凡析构
constant_init<std::string> ci_str; // 构造时初始化空字符串(运行时)
// 析构时未调用 std::string 的析构,内存泄漏

2.constexpr 失效场景

T 的默认构造不是 constexpr(如包含动态内存分配),constant_init 的构造函数将退化为运行时调用,失去编译期初始化优势。

3.复制 / 移动语义缺失

默认生成的复制构造 / 赋值运算符会浅拷贝 obj,若 T 需深拷贝(如自定义带指针的类型),需手动实现拷贝逻辑,否则会导致浅拷贝问题。

6.注意事项

1.添加静态断言(如上文示例),强制检查 T 是平凡析构类型,避免误用。

2.手动实现复制 / 移动语义(若 T 需深拷贝):

cpp 复制代码
constexpr constant_init(const constant_init& other) : obj(other.obj) {}
constant_init& operator=(const constant_init& other) {
    obj = other.obj;
    return *this;
}

3.若需支持 C++20+,直接使用 std::optional 替代,无需自定义结构体。

相关推荐
cchjyq5 分钟前
嵌入式按键调参:简洁接口轻松调参(ADC FLASH 按键 屏幕参数显示)
c语言·c++·单片机·mcu·开源·开源软件
程序炼丹师5 分钟前
std::runtime_error是否会终止程序
c++
mg6686 分钟前
0基础开发学习python工具_____用 Python + Pygame 打造绚丽烟花秀 轻松上手体验
开发语言·python·学习·pygame
qq_433554546 分钟前
C++字符串hash
c++·算法·哈希算法
无限进步_7 分钟前
【C语言】堆(Heap)的数据结构与实现:从构建到应用
c语言·数据结构·c++·后端·其他·算法·visual studio
CodeOfCC17 分钟前
C++ 实现ffmpeg解析hls fmp4 EXT-X-DISCONTINUITY并支持定位
开发语言·c++·ffmpeg·音视频
ghie909017 分钟前
基于LSB匹配的隐写术MATLAB实现
开发语言·计算机视觉·matlab
w陆压19 分钟前
9.野指针和悬空指针
c++·c++基础知识
Lhan.zzZ19 分钟前
Qt绘制残留问题排查与修复日志
开发语言·数据库·qt
CodeAllen嵌入式23 分钟前
Rust 正式成为 Linux 永久核心语言
linux·开发语言·rust