C++中union与variant的定义与使用

发布于 2024-08-14  39 次阅读


C++中union与variant的定义与使用

union

union 常用于需要节省内存的地方,比如嵌入式系统中,或者需要对同一块数据进行不同类型的解释时,比如网络数据包解析。

union 的定义方式与 struct 类似,但所有成员共享同一块内存。以下是一个简单的例子:

union Data {
    int i;
    float f;
    char str[20];
};

可以像使用 struct 一样使用 union

#include <iostream>
#include <cstring>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    Data data;

    // 使用整数成员
    data.i = 10;
    std::cout << "data.i: " << data.i << std::endl;

    // 使用浮点数成员,覆盖整数成员
    data.f = 220.5;
    std::cout << "data.f: " << data.f << std::endl;

    // 使用字符串成员,覆盖浮点数成员
    std::strcpy(data.str, "C++ Union");
    std::cout << "data.str: " << data.str << std::endl;

    // 注意:由于union的特点,访问非当前活动的成员会导致未定义行为
    std::cout << "data.i (undefined behavior): " << data.i << std::endl;
    std::cout << "data.f (undefined behavior): " << data.f << std::endl;

    return 0;
}

union Data 可以存储一个整数、一个浮点数或一个字符串。但在任何时刻,union 只能存储这三种类型中的一种。当你向 union 中存储一个新值时,之前存储的值将被覆盖。

注意事项

  1. 未定义行为:由于 union 的所有成员共享相同的内存位置,只有最后存储的值是有效的。如果访问一个存储在 union 中但当前未活动的成员,行为是未定义的。
  2. 内存对齐:在某些架构中,不同数据类型的对齐要求可能不同。这可能会导致访问未对齐数据时的性能问题或崩溃。
  3. 构造函数和析构函数:在 C++ 中,如果 union 包含非平凡的构造函数或析构函数,编译器会生成这些函数并在适当的时候调用它们。

内存地址

假设union的内存起始地址是0,编译器会确保每个成员都对齐到适当的边界:

  • intfloat成员需要对齐到4字节边界。
  • char str[9]成员需要对齐到1字节边界,但为了对齐其他成员,整个union的大小会提升到8的倍数。
  • double成员需要对齐到8字节边界。

为了满足所有这些对齐要求,union的总大小会是16字节,这是为了确保所有成员都能正确对齐和访问。

variant ---C++17

  1. 定义

    #include 
    #include 
    #include 
    
    std::variant data;
  2. 赋值

    data = 10;               // 现在 data 存储的是 int
    data = 3.14f;            // 现在 data 存储的是 float
    data = "Hello, world!";  // 现在 data 存储的是 string
  3. 访问: 使用 std::get 来访问存储的值,必须确保访问的类型正确。

    try {
       std::cout << std::get(data) << std::endl;
       std::cout << std::get(data) << std::endl;  // 这里会抛出异常,因为当前存储的是 string
    } catch (const std::bad_variant_access& e) {
       std::cout << e.what() << std::endl;
    }
  4. 检查存储的类型: 使用 std::holds_alternative 来检查 variant 当前存储的类型。

    if (std::holds_alternative(data)) {
       std::cout << "Data holds an int." << std::endl;
    } else {
       std::cout << "Data does not hold an int." << std::endl;
    }
  5. 访问当前值: 使用 std::visit 可以访问存储的任意类型的值,这个函数会根据当前存储的类型来调用相应的函数。

    std::visit([](auto&& arg) {
       std::cout << arg << std::endl;
    }, data);

variant 适用于需要在同一个变量中根据上下文存储不同类型数据的场景,提供了灵活而安全的类型处理方式。

对比

union

  • union 很简单,不支持存储有非平凡构造函数、复制构造函数、移动构造函数或析构函数的类型。
  • 只使用一块内存空间,按照最大的来

variant

  • variant 支持存储任何类型,包括带有复杂生命周期管理的类(如具有自定义构造函数和析构函数的类)。此外,variant 还支持递归变体、访问者模式等高级功能。
  • 多块内存空间根据需要的增加开辟(加法开辟)
QQ:2219349024
最后更新于 2024-08-14