名稱空間
變體
操作

聯合宣告

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

聯合體是一種特殊的類型別,它在任何給定時間只能容納其非靜態資料成員中的一個。

目錄

[編輯] 語法

聯合體宣告的類說明符類似於類或結構體宣告

union attr class-head-name { member-specification }
屬性 - (C++11 起) 可選的任意數量屬性序列
class-head-name - 正在定義的聯合體的名稱。可選地,前置nested-name-specifier(名稱和作用域解析運算子序列,以作用域解析運算子結尾)。名稱可以省略,在這種情況下,聯合體是匿名的
member-specification - 訪問說明符、成員物件和成員函式宣告及定義的列表。

聯合體可以有成員函式(包括建構函式和解構函式),但不能有虛擬函式。

聯合體不能有基類,也不能用作基類。

最多隻有一個變體成員可以有預設成員初始化器

(C++11 起)

聯合體不能有引用型別的非靜態資料成員。

聯合體不能包含具有非平凡特殊成員函式的非靜態資料成員。

(C++11 前)

如果聯合體包含具有非平凡特殊成員函式的非靜態資料成員,則聯合體的相應特殊成員函式可以定義為已刪除,詳情請參閱相應的特殊成員函式頁面。

(C++11 起)

就像在結構體宣告中一樣,聯合體中的預設成員訪問是公共的

[編輯] 解釋

聯合體至少與其最大的資料成員一樣大,但通常不會更大。其他資料成員旨在分配在與該最大成員相同的位元組中。這種分配的細節是實現定義的,除了所有非靜態資料成員具有相同的地址。讀取最近未寫入的聯合體成員是未定義行為。許多編譯器作為非標準語言擴充套件實現讀取聯合體非活動成員的能力。

#include <cstdint>
#include <iostream>
 
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};                      // the whole union occupies 4 bytes
 
int main()
{
    S s = {0x12345678}; // initializes the first member, s.n is now the active member
    // At this point, reading from s.s or s.c is undefined behavior,
    // but most compilers define it.
    std::cout << std::hex << "s.n = " << s.n << '\n';
 
    s.s[0] = 0x0011; // s.s is now the active member
    // At this point, reading from s.n or s.c is undefined behavior,
    // but most compilers define it.
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}

可能的輸出

s.n = 12345678
s.c is now 0
s.n is now 115678

每個成員的分配都如同它是該類的唯一成員。

如果聯合體的成員是具有使用者定義建構函式和解構函式的類,則要切換活動成員,通常需要顯式解構函式和 placement new。

#include <iostream>
#include <string>
#include <vector>
 
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))
 
int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    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 會導致未定義行為,則在指定的儲存中隱式建立型別為 X 的物件;不執行初始化,並且其生命週期的開始在左運算元和右運算元的求值之後,賦值之前進行。

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
 
int f()
{
    C c;               // does not start lifetime of any union member
    c.b.a.y[3] = 4;    // OK: "c.b.a.y[3]", names union members c.b and c.b.a.y;
                       // This creates objects to hold union members c.b and c.b.a.y
    return c.b.a.y[3]; // OK: c.b.a.y refers to newly created object
}
 
struct X { const int a; int b; };
union Y { X x; int k; };
 
void g()
{
    Y y = {{1, 2}}; // OK, y.x is active union member
    int n = y.x.a;
    y.k = 4;   // OK: ends lifetime of y.x, y.k is active member of union
    y.x.b = n; // undefined behavior: y.x.b modified outside its lifetime,
               // "y.x.b" names y.x, but X's default constructor is deleted,
               // so union member y.x's lifetime does not implicitly start
}

聯合體型別中的平凡移動建構函式、移動賦值運算子、(C++11 起)複製建構函式和複製賦值運算子複製物件表示。如果源和目標不是同一個物件,這些特殊成員函式在執行復制之前,啟動目標中巢狀的與源中對應的每個物件(除了既不是目標子物件也不是隱式生命週期型別的物件)的生命週期。否則,它們不執行任何操作。透過平凡特殊函式構造或賦值後,兩個聯合體物件具有相同的相應活動成員(如果有)。

[編輯] 匿名聯合體

匿名聯合體是未命名的聯合體定義,它不同時定義任何變數(包括聯合體型別的物件、引用或指向聯合體的指標)。

union { member-specification } ;

匿名聯合體有進一步的限制:它們不能有成員函式,不能有靜態資料成員,並且所有資料成員都必須是公共的。只允許非靜態資料成員static_assert宣告(C++11 起)

匿名聯合體的成員被注入到封閉作用域中(並且不能與在那裡宣告的其他名稱衝突)。

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

名稱空間範圍的匿名聯合體必須宣告為static,除非它們出現在匿名名稱空間中。

[編輯] 類聯合體

類聯合體要麼是聯合體,要麼是(非聯合體)類,其中至少包含一個匿名聯合體作為成員。類聯合體有一組變體成員

  • 其成員匿名聯合體的非靜態資料成員;
  • 此外,如果類聯合體是聯合體,則其不是匿名聯合體的非靜態資料成員。

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

#include <iostream>
 
// S has one non-static data member (tag), three enumerator members (CHAR, INT, DOUBLE), 
// and three variant members (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 聯合體 [class.union]
  • C++20 標準 (ISO/IEC 14882:2020)
  • 11.5 聯合體 [class.union]
  • C++17 標準 (ISO/IEC 14882:2017)
  • 12.3 聯合體 [class.union]
  • C++14 標準 (ISO/IEC 14882:2014)
  • 9.5 聯合體 [class.union]
  • C++11 標準 (ISO/IEC 14882:2011)
  • 9.5 聯合體 [class.union]
  • C++03 標準 (ISO/IEC 14882:2003)
  • 9.5 聯合體 [class.union]
  • C++98 標準 (ISO/IEC 14882:1998)
  • 9.5 聯合體 [class.union]

[編輯] 另請參閱

(C++17)
一種型別安全的帶判別聯合體
(類模板) [編輯]
C 文件 for 聯合宣告