Share via


union

注意

在 C++17 和更新版本中, std::variantclass 是 的 union 型別安全替代方案。

union是使用者定義的類型,其中所有成員共用相同的記憶體位置。 此定義表示在任何指定時間, union 都可以在其成員清單中包含不超過一個物件。 這也表示,無論 有多少 union 成員,它一律只會使用足夠的記憶體來儲存最大的成員。

union當您有許多物件和有限的記憶體時,可以節省記憶體。 不過, union 需要特別小心才能正確使用。 您必須負責確保您一律存取您指派的相同成員。 如果任何成員類型具有非trivial con struct 或 ,則您必須撰寫程式碼來明確 con struct 並終結該成員。 使用 union 之前,請考慮您嘗試解決的問題是否更能透過使用基底 class 和衍生 class 類型來表示。

語法

uniontagopt{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;
}

在上一個範例中, union 中的 Inputstruct 沒有名稱,因此稱為 匿名 union 。 其成員可以直接存取,就像是 的成員一 struct 樣。 如需如何使用匿名 union 的詳細資訊,請參閱 匿名 union 一節。

上一個範例顯示您也可以使用 class 衍生自通用基底 class 的類型來解決的問題。 您可以根據容器中每個物件的執行時間類型來分支程式碼。 您的程式碼可能更容易維護和瞭解,但也可能比使用 union 慢。 此外,您也可以使用 union 來儲存不相關的型別。 union可讓您動態變更預存值的型別,而不需要變更變數本身的類型 union 。 例如,您可以建立 的 MyUnionType 異質陣列,其元素會儲存不同類型的不同值。

在範例中很容易誤用 Inputstruct 。 使用者必須正確地使用歧視性來存取保存資料的成員。 您可以藉由建立 unionprivate 和 提供特殊存取功能,以防止誤用,如下一個範例所示。

不受限制 union (C++11)

在 C++03 和更早版本中, union 可以包含具有 class 型別的非 static 資料成員,只要類型沒有使用者提供 con struct 或 de struct ors 或 assignment 運算子。 在 C++11 中,已移除這些限制。 如果您在 中 union 加入這類成員,編譯器會自動將不是使用者提供的任何特殊成員函式標示為 deleted 。 union如果 是 或 struct 內的 class 匿名 union ,則 或 的任何特殊成員函式,或 struct 未提供使用者的任何特殊成員函 class 式都會標示為 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 以記憶體方式排列 ,如下圖所示:

Diagram that shows the overlapping storage of data in the NumericType union.

此圖顯示 8 個位元組的資料。 雙精度浮點數類型 dValue 會佔用整個 8 個位元組。 long lValue 類型會佔用前 4 個位元組。 簡短類型 iValue 會佔用第一個位元組。

匿名 union

匿名 union 是宣告但不含 class-namedeclarator-list 的 。

union { member-list }

匿名 union 宣告的名稱會直接使用,例如非成員變數。 這表示匿名 union 中宣告的名稱在周圍範圍中必須是唯一的。

匿名 union 受限於下列限制:

  • 如果在檔案或命名空間範圍中宣告,它也必須宣告為 static
  • 它只能 public 有成員;在匿名 union 中擁有 privateprotected 成員會產生錯誤。
  • 它不能有成員函式。

另請參閱

類別和結構
關鍵字
class
struct