自定义类型进阶:联合与枚举

C++ 自定义类型进阶:联合(Union)与枚举(Enum)

联合和枚举是 C++ 中两种重要的自定义类型,它们在内存布局、类型安全和表达能力上各有特色。现代 C++(C++11 及以后)对两者都进行了显著增强。

1. 枚举(Enumeration)

枚举用于定义一组具名整数常量,提高代码可读性和类型安全性。

1.1 传统 enum vs enum class(强烈推荐后者)

// 传统 enum(作用域渗漏)
enum Color { RED, GREEN, BLUE };

// enum class(作用域枚举,C++11 起)
enum class Status : int {  // 可显式指定底层类型
    Success = 0,
    Failure = 1,
    Timeout = 2
};

主要区别

特性传统 enumenum class (scoped enum)
作用域渗漏到外围作用域严格限定在枚举名内
隐式转换可隐式转为 int不允许隐式转换
底层类型默认 int,可指定可指定(intcharuint64_t 等)
前向声明困难容易(需指定底层类型)
std::hash 等配合较差优秀

推荐写法(现代 C++):

enum class LogLevel : uint8_t {
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal
};

// 使用
LogLevel level = LogLevel::Info;
// int i = level;           // 错误!不允许隐式转换
int i = static_cast<int>(level);   // 显式转换

// 枚举类与 switch 配合完美
switch (level) {
    case LogLevel::Debug:   ... break;
    // case 必须使用 LogLevel::XXX,不会有名字冲突
}

1.2 进阶用法

  • 底层类型控制std::underlying_type_t<LogLevel>
  • 枚举类 + using 别名 提升可读性
  • 位标志(Bit Flags):使用 enum class + 运算符重载实现类型安全的位操作
enum class Permission : unsigned {
    Read  = 1 &lt;&lt; 0,
    Write = 1 &lt;&lt; 1,
    Exec  = 1 &lt;&lt; 2,
    All   = Read | Write | Exec
};

// 运算符重载(示例)
constexpr Permission operator|(Permission a, Permission b) {
    return static_cast&lt;Permission>(
        static_cast&lt;unsigned>(a) | static_cast&lt;unsigned>(b));
}
  • 反射支持(C++26 实验性或第三方):枚举名字符串转换等。

2. 联合(Union)

联合是一种节省内存的类型,所有成员共享同一块内存(大小等于最大的非静态成员)。

2.1 基本用法与限制

union Data {
    int i;
    float f;
    char str[8];
};  // sizeof(Data) == 8(取决于平台)

重要规则(C++ 核心):

  • 同一时刻只能有一个成员是“活跃的”(active)
  • 读非活跃成员属于未定义行为(UB)
  • C++11 前:不能包含拥有非平凡构造函数/析构函数的成员。
  • C++11 后:允许,但需要手动管理生命周期(placement new / 显式析构)。

2.2 匿名联合(Anonymous Union)

常用于结构体内部节省内存:

struct Variant {
    int type;
    union {          // 匿名联合
        int i;
        double d;
        std::string s;   // C++11 后允许(需手动管理)
    };
};

2.3 进阶:带非平凡成员的 Union

union U {
    int i;
    std::string s;     // 有构造函数

    U() : i(0) {}                     // 默认构造 int
    U(const std::string&amp; str) : s(str) {}
    ~U() { if (active == 2) s.~basic_string(); }  // 必须手动析构

private:
    int active = 1;   // 跟踪活跃成员
};

强烈建议:不要手动写这种联合,使用 std::variant 替代。

3. std::variant —— 现代安全的“类型安全的联合”

C++17 引入,是联合的类型安全升级版

#include &lt;variant>
#include &lt;string>

using Value = std::variant&lt;int, double, std::string>;

Value v = 42;
v = 3.14;
v = "hello";

// 访问方式
if (auto* p = std::get_if&lt;int>(&amp;v)) {
    std::cout &lt;&lt; *p;
}

// 使用 std::visit(推荐,最优雅)
std::visit([](auto&amp;&amp; arg) {
    using T = std::decay_t&lt;decltype(arg)>;
    if constexpr (std::is_same_v&lt;T, int>) { ... }
    else if constexpr (...) { ... }
}, v);

优点

  • 类型安全,无 UB
  • 自动管理生命周期
  • 支持 std::monostate 处理无值情况
  • 可持有任意类型(包括非平凡)

性能:与手工 union 几乎无差别,但更安全。

4. 最佳实践总结

  1. 枚举
  • 永远优先使用 enum class
  • 需要与 C 代码交互时才使用传统 enum
  • 给底层类型指定合适的大小(uint8_tuint32_t 等)。
  1. 联合
  • 简单 POD 类型(如数值、指针)可直接用 union
  • 复杂类型 → 使用 std::variant
  • 需要极致性能 + 手动控制时,才考虑带生命周期管理的 union(极少见)。
  1. 组合使用
   enum class Type { Int, Double, String };
   struct Any {
       Type t;
       std::variant&lt;int, double, std::string> v;
   };
  1. 内存对齐与 [[no_unique_address]](C++20)在更复杂的自定义类型中也很重要。

5. 常见陷阱

  • 读了非活跃的 union 成员 → UB(可能崩溃、返回垃圾值)。
  • enum class 忘记 static_cast 导致编译错误(这是特性,不是 bug)。
  • std::variant 中持有引用类型(不允许,必须用 std::reference_wrapper)。
  • 跨平台时注意枚举底层类型和 union 对齐。

掌握 enum class + std::variant 后,你的自定义类型会变得更加类型安全、表达力强且现代。传统 union 和 enum 主要在与遗留代码、嵌入式、驱动等场景仍有价值。

如果你想深入以下任一主题,告诉我:

  • std::variant 的完整实现技巧与 std::visit 进阶
  • 位字段(Bit-field)与联合的结合
  • 枚举的反射 / 序列化
  • Tagged Union 手写模式 vs variant
  • 在性能敏感代码中的优化案例

继续下一部分吗?(如 std::optionalstd::any、CRTP、Type Erasure 等)

文章已创建 5321

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部