聯合體 (Union) 宣告
聯合體 (Union) 是一種特殊的類別型別,在同一時間只能保存其非靜態 資料成員 中的其中一個。
目錄 |
[編輯] 語法
聯合體宣告的類別說明符與 class 或 struct 宣告相似
union 屬性 類別頭名稱 { 成員規格 } |
|||||||||
| 屬性 | - | (C++11 起) 可選的任意數量的屬性序列 |
| 類別頭名稱 (class-head-name) | - | 所定義聯合體的名稱。可選擇在前方加上 嵌套名稱說明符(名稱與作用域解析運算子的序列,以作用域解析運算子結尾)。名稱可以省略,在此情況下該聯合體為未命名的。 |
| 成員規格 (member-specification) | - | 存取說明符、成員物件及成員函式宣告與定義的列表。 |
聯合體可以擁有成員函式(包括建構函式與解構函式),但不能有虛擬函式。
聯合體不能擁有基底類別,也不能作為基底類別使用。
| (C++11 起) |
聯合體不能擁有參考型別的非靜態資料成員。
|
聯合體不能包含具有非平凡(non-trivial)特殊成員函式的非靜態資料成員。 |
(直到 C++11) |
|
若聯合體包含具有非平凡 特殊成員函式 的非靜態資料成員,則該聯合體對應的特殊成員函式可能會被定義為刪除(deleted),詳細資訊請參閱對應的特殊成員函式頁面。 |
(C++11 起) |
就像在 struct 宣告中一樣,聯合體中的預設成員存取權限為 public(公開)。
[編輯] 解釋
聯合體的大小至少足以容納其最大的資料成員,但通常不會更大。其他資料成員旨在與該最大成員共享相同的位元組空間。關於該配置的細節由實作定義,但所有非靜態資料成員皆擁有相同的位址。讀取聯合體中非最近寫入的成員屬於未定義行為。許多編譯器作為非標準語言擴充功能,實作了讀取聯合體不活躍成員的能力。
#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 起) |
若聯合體的兩個成員皆為 標準佈局 (standard-layout) 型別,在任何編譯器上檢查它們的共同子序列都是明確定義的。
[編輯] 成員生命週期
聯合體成員的 生命週期 始於該成員變為活躍時。若先前有另一個成員是活躍的,則其生命週期結束。
當聯合體的活躍成員透過形式為 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; // 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 { 成員規格 } ; |
|||||||||
匿名聯合體有進一步的限制:它們不能擁有成員函式,不能擁有靜態資料成員,且所有資料成員必須是公開的。唯一允許的宣告是非靜態資料成員 以及 static_assert 宣告(C++11 起)。
匿名聯合體的成員會被注入至封閉作用域中(且不得與該處宣告的其他名稱衝突)。
int main() { union { int a; const char* p; }; a = 1; p = "Jennifer"; }
命名空間作用域的匿名聯合體必須宣告為 static,除非它們出現在未命名的命名空間中。
[編輯] 類聯合體類別
類聯合體類別 指的是聯合體,或是擁有至少一個匿名聯合體作為成員的(非聯合體)類別。類聯合體類別擁有一組 變體成員:
- 其成員匿名聯合體中的非靜態資料成員;
- 此外,若該類聯合體類別本身為聯合體,則包括其非匿名聯合體的非靜態資料成員。
類聯合體類別可用於實作 標記聯合體 (tagged union)。
#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 起) |
[編輯] 關鍵字
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯應用於之前的 C++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| 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) |
一種型別安全的識別聯合體 (discriminated union) (類別模板) |
| C 文件 關於 聯合體宣告
| |