联合体声明

出自cppreference.com


 
 
C++ 語言
 
 

聯合體是特殊的類類型,它在一個時刻只能保有其一個非靜態數據成員

語法

聯合體聲明的類說明符與類或結構體的聲明相似:

union 屬性 類頭名 { 成員說明 }
屬性 - (C++11 起) 任意數量屬性的可選序列
類頭名 - 所定義的聯合體的名字。可以前附嵌套名說明符(名字與作用域解析運算符的序列,以作用域解析運算符結尾)。可以忽略名字,此時聯合體是無名 
成員說明 - 訪問說明符、成員對象和成員函數的聲明與定義的列表。

聯合體可以擁有成員函數(包含構造函數和析構函數),但不能有虛函數。

聯合體不能有基類且不能用作基類。

最多只有一個變體成員可以擁有默認成員初始化器

(C++11 起)

聯合體不能擁有引用類型的非靜態數據成員。

正如結構體的聲明中一般,聯合體的默認成員訪問是 public

解釋

聯合體的大小至少足以保有其最大的數據成員,但通常不會更大。其他數據成員特意分配於該最大成員的一部分相同的字節之中。分配的細節是實現定義的,但所有非靜態數據成員均擁有相同的地址。讀取並非最近寫入的聯合體成員是未定義行為。許多編譯器以非標準語言擴展實現讀取聯合體的不活躍成員的能力。

#include <cstdint>
#include <iostream>

union S
{
    std::int32_t n;     // 占用 4 字节
    std::uint16_t s[2]; // 占用 4 字节
    std::uint8_t c;     // 占用 1 字节
};                      // 整个联合体占用 4 字节

int main()
{
    S s = {0x12345678}; // 初始化首个成员,s.n 现在是活跃成员
    // 在此点,从 s.s 或 s.c 读取是未定义行为,但大多数编译器都对其有定义。
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s 现在是活跃成员
    // 在此点,从 s.n 或 s.c 读取是未定义行为,但大多数编译器都对其有定义。
    std::cout << "s.c 现在是 " << +s.c << '\n' // 11 或 00,取决于平台
              << "s.n 现在是 " << s.n << '\n'; // 12340011 或 00115678
}

可能的輸出:

s.n = 12345678
s.c 现在是 0
s.n 现在是 115678

每個成員的分配都如同它是類的唯一成員一樣來進行。

聯合體不能含有帶非平凡的特殊成員函數的非靜態數據成員。

(C++11 前)

如果聯合體含有帶非平凡的特殊成員函數的非靜態數據成員,那麼該聯合體的對應特殊成員函數可能會被定義為棄置,詳情見對應的特殊成員函數頁面。

(C++11 起)

如果聯合體的成員是擁有用戶定義的構造函數和析構函數的類,那麼切換其活躍成員通常需要顯式析構函數和布置 new:

#include <iostream>
#include <string>
#include <vector>

union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // 需要知道哪个成员活跃,只能在联合体式的类中做到
};          // 整个联合体占有 max(sizeof(string), sizeof(vector<int>)) 的内存

int main()
{
    S s = {"Hello, world"};
    // 在此点,从 s.vec 读取是未定义行为
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // 现在,s.vec 是联合体的活跃成员
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

輸出:

s.str = Hello, world
1
(C++11 起)

如果兩個聯合體成員都是標準布局類型,那麼在任何編譯器上檢驗其公共子序列都有良好定義。

成員生存期

聯合體成員的生存期從該成員被設為活躍時開始。如果之前已經有另一成員活躍,那麼此前已有的這一成員生存期終止。

當聯合體的活躍成員通過形式為 E1 = E2 的複製表達式(使用內建賦值運算符或平凡的賦值運算符)切換時,對於 E1 中的各個成員訪問和數組下標子表達式中出現的,其類型並非擁有非平凡或棄置的默認構造函數的類的每個聯合體成員 X,如果 X 的修改在類型別名使用規則下會具有未定義行為,那麼在所指名的存儲中隱式創建一個 X 類型的對象;不進行初始化,且其生存期的開始按順序晚於其左右的操作數的值計算,而早於賦值。

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };

int f()
{
    C c;               // 不开始任何联合体成员的生存期
    c.b.a.y[3] = 4;    // OK:"c.b.a.y[3]" 指名联合体成员 c.b 与 c.b.a.y;
                       // 这创建对象以保有联合体成员 c.b 和 c.b.a.y
    return c.b.a.y[3]; // OK:c.b.a.y 指代新创建的对象
}

struct X { const int a; int b; };
union Y { X x; int k; };

void g()
{
    Y y = {{1, 2}}; // OK,y.x 是联合体的活跃成员
    int n = y.x.a;
    y.k = 4;   // OK:结束 y.x 的生存期,y.k 是联合体的活跃成员
    y.x.b = n; // 未定义行为:y.x.b 在其生存期外被修改,
               // "y.x.b" 指名 y.x,但 X 的默认构造函数被弃置,
               // 所以联合体成员 y.x 的生存期不会隐式开始
}

聯合體類型的平凡移動構造函數、移動賦值運算符、(C++11 起)複製構造函數和複製賦值運算符複製對象表示。如果源與目標不是同一對象,那麼這些特殊成員函數在複製前開始每個內嵌於目標的並對應內嵌於源的對象(除了既非目標的子對象亦不擁有隱式生存期類型的對象)的生存期。否則,它們不做任何事。在經由平凡特殊成員函數構造或賦值後,兩個聯合體對象擁有相同的對應活躍成員(如果存在)。

匿名聯合體

匿名聯合體 是不同時定義任何變量(包括聯合體類型的對象、引用或指向聯合體的指針)的無名的聯合體定義。

union { 成員說明 } ;

匿名聯合體有更多限制:它們不能有成員函數,不能有靜態數據成員,且所有數據成員必須公開。只能聲明非靜態數據成員,外加static_assert 聲明(C++11 起)

匿名聯合體的成員被注入到它的外圍作用域中(而且不得與其中聲明的其他名字衝突)。

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Jennifer";
}

命名空間作用域的匿名聯合體必須聲明為 static,除非它們在無名命名空間出現。

聯合體式的類

聯合體式的類 是聯合體,或是至少擁有一個匿名聯合體成員的(非聯合)類。聯合體式的類擁有一組變體成員

  • 其成員匿名聯合體的非靜態數據成員;
  • 另外,如果聯合體式的類是聯合體,那麼是其並非匿名聯合體的非靜態數據成員。

聯合體式的類可用於實現帶標籤聯合體

#include <iostream>

// S 拥有一个非静态数据成员(tag),三个枚举项成员(CHAR、INT、DOUBLE),
// 和三个变体成员(c、i、d)
struct S
{
    enum{CHAR, INT, DOUBLE} tag;
    union
    {
        char c;
        int i;
        double d;
    };
};

void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.i << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}

int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT;
    s.i = 123;
    print_s(s);
}

輸出:

a
123

C++ 標準庫包含 std::variant,它可以取代聯合體和聯合體式的類的大多數用途。上例可重寫為

#include <iostream>
#include <variant>

int main()
{
    std::variant<char, int, double> s = 'a';
    std::visit([](auto x){ std::cout << x << '\n';}, s);
    s = 123;
    std::visit([](auto x){ std::cout << x << '\n';}, s);
}

輸出:

a
123
(C++17 起)

關鍵詞

union

缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 出版時的行為 正確行為
CWG 1940 C++11 匿名聯合體只允許非靜態數據成員 也允許 static_assert

引用

  • C++23 標準(ISO/IEC 14882:2024):
  • 11.5 Unions [class.union]
  • C++20 標準(ISO/IEC 14882:2020):
  • 11.5 Unions [class.union]
  • C++17 標準(ISO/IEC 14882:2017):
  • 12.3 Unions [class.union]
  • C++14 標準(ISO/IEC 14882:2014):
  • 9.5 Unions [class.union]
  • C++11 標準(ISO/IEC 14882:2011):
  • 9.5 Unions [class.union]
  • C++03 標準(ISO/IEC 14882:2003):
  • 9.5 Unions [class.union]
  • C++98 標準(ISO/IEC 14882:1998):
  • 9.5 Unions [class.union]

參閱

(C++17)
類型安全的可辨識聯合體
(類模板) [編輯]
聯合體聲明C 文檔