union

注意

在 C++17 及更高版本中,std::variantclass 是 union 的一个类型安全替代项。

union 是一个用户定义类型,其中所有成员都共享同一个内存位置。 此定义意味着在任何给定时间,union 都不能包含来自其成员列表的多个对象。 这还意味着,无论 union 具有多少成员,它始终仅使用足以存储最大成员的内存。

如果具有大量对象但内存有限,union 可用于节省内存。 但是,需要格外小心才能正确使用 union。 你负责确保始终访问分配的同一成员。 如果任何成员类型具有不常用构造函数,则必须编写代码来显式构造和销毁该成员。 使用 union 之前,应考虑是否可以使用基 class 和派生的 class 类型来更好地表示尝试解决的问题。

语法

union tag opt{ member-list };

参数

tag
为 union 提供的类型名称。

member-list
union 可以包含的成员。

声明 union

利用 union 关键字开始 union 的声明,并用大括号包含成员列表:

// declaring_a_union.cpp
union RecordType    // Declare a simple union type
{
    char   ch;
    int    i;
    long   l;
    float  f;
    double d;
    int *int_ptr;
};

int main()
{
    RecordType t;
    t.i = 5; // t holds an int
    t.f = 7.25; // t now holds a float
}

使用 union

在上面的示例中,任何访问 union 的代码都需要了解保存数据的成员。 解决此问题的最常见解决方案被称为“可区分 union”。 它将 union 括在 struct 中,还加入一个 enum 成员,用于指示当前存储在 union 中的成员类型。 下面的示例演示了基本模式:

#include <queue>

using namespace std;

enum class WeatherDataType
{
    Temperature, Wind
};

struct TempData
{
    int StationId;
    time_t time;
    double current;
    double max;
    double min;
};

struct WindData
{
    int StationId;
    time_t time;
    int speed;
    short direction;
};

struct Input
{
    WeatherDataType type;
    union
    {
        TempData temp;
        WindData wind;
    };
};

// Functions that are specific to data types
void Process_Temp(TempData t) {}
void Process_Wind(WindData w) {}

void Initialize(std::queue<Input>& inputs)
{
    Input first;
    first.type = WeatherDataType::Temperature;
    first.temp = { 101, 1418855664, 91.8, 108.5, 67.2 };
    inputs.push(first);

    Input second;
    second.type = WeatherDataType::Wind;
    second.wind = { 204, 1418859354, 14, 27 };
    inputs.push(second);
}

int main(int argc, char* argv[])
{
    // Container for all the data records
    queue<Input> inputs;
    Initialize(inputs);
    while (!inputs.empty())
    {
        Input const i = inputs.front();
        switch (i.type)
        {
        case WeatherDataType::Temperature:
            Process_Temp(i.temp);
            break;
        case WeatherDataType::Wind:
            Process_Wind(i.wind);
            break;
        default:
            break;
        }
        inputs.pop();

    }
    return 0;
}

在上面的示例中,Inputstruct 中的 union 没有名称,因此被称为“匿名 union”。 它的成员是可以直接访问的,就像它们是 struct 的成员一样。 有关如何使用匿名 union 的详细信息,请参阅“匿名 union”部分。

上面的示例展示的问题也可以通过以下方法解决:使用派生自公共基 class 的 class 类型。 基于容器中每个对象的运行时类型对代码进行分支。 代码将更易于维护和理解,但是也可能比使用 union 更慢。 此外,通过 union 可以存储不相关的类型。 union 让你可以动态地更改存储值的类型,而无需更改 union 变量本身的类型。 例如,可以创建其元素存储不同类型的不同值的 MyUnionType 异类数组。

很容易误用示例中的 Inputstruct。 由用户负责正确使用鉴别器来访问保存数据的成员。 你可以通过使 union 成为 private 并提供特殊访问函数(如下一个示例所示)来防止误用。

不受限制的 union (C++11)

在 C++03 及更低版本中,union 可以包含具有 class 类型的非静态数据成员,只要该类型没有用户提供的构造函数、析构函数或赋值运算符即可。 在 C++11 中,消除了这些限制。 如果在 union 中包含这样一个成员,编译器会自动将不是用户提供的任何特殊成员函数标记为 deleted。 如果 union 是 class 或 struct 中的匿名 union,则 class 或 struct 的非用户提供的任何特殊成员函数都会标记为 deleted。 以下示例展示了如何处理此情况。 union 的某个成员具有需要此特殊处理的成员:

// for MyVariant
#include <crtdbg.h>
#include <new>
#include <utility>

// for sample objects and output
#include <string>
#include <vector>
#include <iostream>

using namespace std;

struct A
{
    A() = default;
    A(int i, const string& str) : num(i), name(str) {}

    int num;
    string name;
    //...
};

struct B
{
    B() = default;
    B(int i, const string& str) : num(i), name(str) {}

    int num;
    string name;
    vector<int> vec;
    // ...
};

enum class Kind { None, A, B, Integer };

#pragma warning (push)
#pragma warning(disable:4624)
class MyVariant
{
public:
    MyVariant()
        : kind_(Kind::None)
    {
    }

    MyVariant(Kind kind)
        : kind_(kind)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A();
            break;
        case Kind::B:
            new (&b_) B();
            break;
        case Kind::Integer:
            i_ = 0;
            break;
        default:
            _ASSERT(false);
            break;
        }
    }

    ~MyVariant()
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            a_.~A();
            break;
        case Kind::B:
            b_.~B();
            break;
        case Kind::Integer:
            break;
        default:
            _ASSERT(false);
            break;
        }
        kind_ = Kind::None;
    }

    MyVariant(const MyVariant& other)
        : kind_(other.kind_)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A(other.a_);
            break;
        case Kind::B:
            new (&b_) B(other.b_);
            break;
        case Kind::Integer:
            i_ = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
    }

    MyVariant(MyVariant&& other)
        : kind_(other.kind_)
    {
        switch (kind_)
        {
        case Kind::None:
            break;
        case Kind::A:
            new (&a_) A(move(other.a_));
            break;
        case Kind::B:
            new (&b_) B(move(other.b_));
            break;
        case Kind::Integer:
            i_ = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
        other.kind_ = Kind::None;
    }

    MyVariant& operator=(const MyVariant& other)
    {
        if (&other != this)
        {
            switch (other.kind_)
            {
            case Kind::None:
                this->~MyVariant();
                break;
            case Kind::A:
                *this = other.a_;
                break;
            case Kind::B:
                *this = other.b_;
                break;
            case Kind::Integer:
                *this = other.i_;
                break;
            default:
                _ASSERT(false);
                break;
            }
        }
        return *this;
    }

    MyVariant& operator=(MyVariant&& other)
    {
        _ASSERT(this != &other);
        switch (other.kind_)
        {
        case Kind::None:
            this->~MyVariant();
            break;
        case Kind::A:
            *this = move(other.a_);
            break;
        case Kind::B:
            *this = move(other.b_);
            break;
        case Kind::Integer:
            *this = other.i_;
            break;
        default:
            _ASSERT(false);
            break;
        }
        other.kind_ = Kind::None;
        return *this;
    }

    MyVariant(const A& a)
        : kind_(Kind::A), a_(a)
    {
    }

    MyVariant(A&& a)
        : kind_(Kind::A), a_(move(a))
    {
    }

    MyVariant& operator=(const A& a)
    {
        if (kind_ != Kind::A)
        {
            this->~MyVariant();
            new (this) MyVariant(a);
        }
        else
        {
            a_ = a;
        }
        return *this;
    }

    MyVariant& operator=(A&& a)
    {
        if (kind_ != Kind::A)
        {
            this->~MyVariant();
            new (this) MyVariant(move(a));
        }
        else
        {
            a_ = move(a);
        }
        return *this;
    }

    MyVariant(const B& b)
        : kind_(Kind::B), b_(b)
    {
    }

    MyVariant(B&& b)
        : kind_(Kind::B), b_(move(b))
    {
    }

    MyVariant& operator=(const B& b)
    {
        if (kind_ != Kind::B)
        {
            this->~MyVariant();
            new (this) MyVariant(b);
        }
        else
        {
            b_ = b;
        }
        return *this;
    }

    MyVariant& operator=(B&& b)
    {
        if (kind_ != Kind::B)
        {
            this->~MyVariant();
            new (this) MyVariant(move(b));
        }
        else
        {
            b_ = move(b);
        }
        return *this;
    }

    MyVariant(int i)
        : kind_(Kind::Integer), i_(i)
    {
    }

    MyVariant& operator=(int i)
    {
        if (kind_ != Kind::Integer)
        {
            this->~MyVariant();
            new (this) MyVariant(i);
        }
        else
        {
            i_ = i;
        }
        return *this;
    }

    Kind GetKind() const
    {
        return kind_;
    }

    A& GetA()
    {
        _ASSERT(kind_ == Kind::A);
        return a_;
    }

    const A& GetA() const
    {
        _ASSERT(kind_ == Kind::A);
        return a_;
    }

    B& GetB()
    {
        _ASSERT(kind_ == Kind::B);
        return b_;
    }

    const B& GetB() const
    {
        _ASSERT(kind_ == Kind::B);
        return b_;
    }

    int& GetInteger()
    {
        _ASSERT(kind_ == Kind::Integer);
        return i_;
    }

    const int& GetInteger() const
    {
        _ASSERT(kind_ == Kind::Integer);
        return i_;
    }

private:
    Kind kind_;
    union
    {
        A a_;
        B b_;
        int i_;
    };
};
#pragma warning (pop)

int main()
{
    A a(1, "Hello from A");
    B b(2, "Hello from B");

    MyVariant mv_1 = a;

    cout << "mv_1 = a: " << mv_1.GetA().name << endl;
    mv_1 = b;
    cout << "mv_1 = b: " << mv_1.GetB().name << endl;
    mv_1 = A(3, "hello again from A");
    cout << R"aaa(mv_1 = A(3, "hello again from A"): )aaa" << mv_1.GetA().name << endl;
    mv_1 = 42;
    cout << "mv_1 = 42: " << mv_1.GetInteger() << endl;

    b.vec = { 10,20,30,40,50 };

    mv_1 = move(b);
    cout << "After move, mv_1 = b: vec.size = " << mv_1.GetB().vec.size() << endl;

    cout << endl << "Press a letter" << endl;
    char c;
    cin >> c;
}

union 无法存储引用。 union 也不支持继承。 这意味着不能使用 union 作为基 class,也不能继承自另一个 class 或具有虚拟函数。

初始化 union

可以通过分配包含在括号中的表达式,在相同语句中声明并初始化 union。 计算该表达式并将其分配给 union 的第一个字段。

#include <iostream>
using namespace std;

union NumericType
{
    short       iValue;
    long        lValue;
    double      dValue;
};

int main()
{
    union NumericType Values = { 10 };   // iValue = 10
    cout << Values.iValue << endl;
    Values.dValue = 3.1416;
    cout << Values.dValue << endl;
}
/* Output:
10
3.141600
*/

NumericTypeunion 排列在内存中(从概念上讲),如下图所示:

显示 NumericType union 中数据的重叠存储的关系图。

此图显示了 8 个字节的数据。 双精度类型 dValue 占用整个 8 字节。 长类型 lValue 占用前 4 个字节。 短类型 iValue 占用第一个字节。

匿名 union

匿名 union 是在没有 class-namedeclarator-list 的情况下声明的。

union { member-list }

匿名 union 中声明的名称可直接使用,就像非成员变量一样。 这意味着匿名 union 中声明的名称必须在周边范围中是唯一的。

匿名 union 受以下限制的约束:

  • 如果是在文件或命名空间范围内声明的,还必须将其声明为 static
  • 它只能有 public 成员;在一个匿名的 union 中拥有 private 成员和 protected 成员会产生错误。
  • 它不能具有成员函数。

另请参阅

类和结构
关键字
class
struct