C++ union 的一种妙用法

 更新时间:2026年07月01日 08:25:08   作者:流星雨爱编程  
本文探讨了C++中union的特殊用法,通过分析标准库中的constant_init模板结构体,揭示了union在编译期常量初始化中的巧妙应用,感兴趣的可以了解一下

1.背景

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

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 的任一非静态数据成员具有非平凡的默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运行符、移动赋值运算符或析构函数,则其相应的成员函数将被隐式删除(除非用户显式定义)。

例如:

union S {
    int a;
    std::string str;
};

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

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

1.使用 default member initializer

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.聚合初始化

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.典型用法

#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::string、std::vector),使用该结构体将导致内存泄漏(obj 的析构函数未被调用):

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

2.constexpr 失效场景

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

3.复制 / 移动语义缺失

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

6.注意事项

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

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

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 替代,无需自定义结构体。

到此这篇关于C++ union 的一种妙用法的文章就介绍到这了,更多相关C++ union用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++浮点数类型详情

    C++浮点数类型详情

    这篇文章主要介绍了C++浮点数类型,浮点数是C++的第二组基本类型,它能够表示带小数部分的数字。不仅如此,浮点数的范围也比int更大,可以表示更大范围的数字。下面来我们大家一起来学习学习内容
    2021-11-11
  • 浅谈int8_t int64_t size_t ssize_t的相关问题(详解)

    浅谈int8_t int64_t size_t ssize_t的相关问题(详解)

    下面小编就为大家带来一篇浅谈int8_t int64_t size_t ssize_t的相关问题(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • C++11原子操作详解

    C++11原子操作详解

    这篇文章主要为大家介绍了C++的原子操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-11-11
  • C++用指针变量作为函数的参数接受数组的值的问题详细总结

    C++用指针变量作为函数的参数接受数组的值的问题详细总结

    以下是对C++中用指针变量作为函数的参数接受数组的值的问题进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • 关于C语言操作符的那些事(超级全)

    关于C语言操作符的那些事(超级全)

    这篇文章主要给大家介绍了关于C语言操作符的那些事儿,c语言的操作符有很多,包括算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员,需要的朋友可以参考下
    2021-08-08
  • C C++输入输出基础教程示例详解

    C C++输入输出基础教程示例详解

    当我们在网站做题的时候经常会遇到各种要求的输入输出,而且会有时间超限等多个问题,这时我们就要优化我们的输入输出或者规范我们的输入输出格式,下面介绍C和C++中的输入输出问题,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2023-11-11
  • C语言函数的声明、定义、调用方式

    C语言函数的声明、定义、调用方式

    本文详细介绍了如何在不同情况下定义和使用函数,包括无返回值和有返回值的无参及有参函数,以及如何将数组作为函数参数,总结了两种在Java中声明和调用函数的方式
    2026-02-02
  • C/C++实现经典象棋游戏的示例代码

    C/C++实现经典象棋游戏的示例代码

    中国象棋是起源于中国的一种棋,属于二人对抗性游戏的一种,在中国有着悠久的历史。本文将利用C++实现这一经典游戏,快跟随小编一起学习一下吧
    2022-06-06
  • C++中的6种构造函数举例详解

    C++中的6种构造函数举例详解

    这篇文章主要介绍了C++中的6种构造函数的相关资料,C++中构造函数用于类对象初始化,类型包括默认构造函数、参数化构造函数、拷贝构造函数等,默认构造函数通常不需要参数,编译器会自动生成,除非存在其他构造函数,需要的朋友可以参考下
    2024-10-10
  • C语言详细讲解指针数组的用法

    C语言详细讲解指针数组的用法

    在C语言和C++等语言中,数组元素全为指针变量的数组称为指针数组,指针数组中的元素都必须具有相同的存储类型、指向相同数据类型的指针变量。指针数组比较适合用来指向若干个字符串,使字符串处理更加方便、灵活
    2022-05-05

最新评论