# (廣義的)聯合體
在C++98(或更早)版本中,union的成員類型,必須不含自定義構造/析構函數或者賦值操作符。
```
union U {
int m1;
complex m2; //錯誤(明顯的):complex擁有構造函數
//錯誤(不那么明顯):string的內部數據只能嚴格地由其構造函數,
// 拷貝構造函數,和析構函數去維護
string m3;
};
```
亦即:
```
U u; // 使用哪個成員的構造函數呢?
u.m1 = 1; // 給整型成員賦值
string s = u.m3; //災難:從string成員拷貝
```
顯而易見,把值寫入一個成員,之后又讀取另外一個成員的做法是給自己找麻煩,然而人們往往并非刻意而為(多因失誤導致) 。
C++11對union的限制條件進行了放寬,以允許更靈活廣泛的成員類型。其中值得特別指出的是,帶有自定義構造函數/析構函數的類型現在也可作為union的成員了。此外,為使“靈活”不至成為脫韁野馬,新標準又特別引入了“第四條軍規”(譯注:參見下文),并倡導使用“可識別union”(譯注:參見下文Widget樣例以及Boost::Any和Boost::Variant)。
C++11中的對union的限制條件重新定義如下:
* 不含虛函數(與C++98相同)
* 不含引用成員(與C++98相同)
* 沒有基類(與C++98相同)
* 若union的某個成員的類型含有自定義構造/拷貝/析構函數,那么該union的相應構造/拷貝/析構函數將會被自動“禁用”(譯注:在C++11中我們可以使用delete關鍵字來“禁用”構造/析構函數),隨之而來的后果是:該union不能被實例化成對象。(新增的所謂“第四條軍規”)
例如:
```
union U1 {
int m1;
complex m2; // ok
};
union U2 {
int m1;
string m3; // ok
};
```
上述代碼看起來容易出問題(譯注:例如有人實例化了U2類型的對象并給其m1成員賦值之后,當union析構時,m3的析構函數可能會crash),但是有了第四條軍規,剛才的隱含問題便迎刃而解(譯注:通過編譯錯誤)。即:
```
U1 u; // ok
u.m2 = {1,2}; // ok:給complex成員賦值
U2 u2; // 編譯錯誤: string類含有析構函數,因而U2的析構函數已被自動禁用
//(譯注:析構函數被禁用意味著不允許在棧上實例化U2對象,否則無法析構)
U2 u3 = u2; // 編譯錯誤:string類含有拷貝構造函數,
// 因而U2類型同樣也不能被拷貝構造
```
這樣看來,先前定義的U2幾乎沒什么實際用途了。唯一可能用到這種奇葩union的地方是,把它嵌到結構體里并且額外記錄其“當值”成員類型——也就是所謂的“可識別union”。樣例如下:
```
class Widget { // 用union存儲的“三態”Widget
private:
// 用以實現“可識別union”的“當值”類型標記
enum class Tag { point, number, text } type;
union { // 三態的具體存儲形式
point p; // point類含有構造函數
int i;
string s; // string類含有默認的構造/拷貝/析構函數
};
// ...
// 由于string中存在拷貝函數,所以需要“手工拷貝”union
Widget& operator=(const Widget& w)
{
// 譯注:從text態到text態
if (type==Tag::text && w.type==Tag::text) {
s = w.s; // 直接使用string的賦值運算符
return *this;
}
// 譯注:從text態到其他態,意味著union不再用于存放string
// 由于union的拷貝函數已被自動禁用,
// 所以需要有人手工釋放string原先所占資源
if (type==Tag::text) s.~string(); // 此處需要顯式析構
switch (w.type) {
case Tag::point: p = w.p; break; // 普通的拷貝
case Tag::number: i = w.i; break;
// 譯注:C++98/03標準中的的原地拷貝構造語法
case Tag::text: new(&s)(w.s); break; // placement new
}
type = w.type;
return *this;
}
};
};
```
其他參考文獻:
* [N2544=08-0054] Alan Talbot, Lois Goldthwaite, Lawrence Crowl, and Jens Maurer: [Unrestricted unions (Revison 2)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf)
(翻譯:張瀟)
- C++11 FAQ中文版 - C++11 FAQ
- Stroustrup先生關于中文版的授權許可郵件
- Stroustrup先生關于C++11 FAQ的一些說明
- 關于C++11的一般性的問題
- 您是如何看待C++11的?
- 什么時候C++0x會成為一部正式的標準呢?
- 編譯器何時將會實現C++11標準呢?
- 我們何時可以用到新的標準庫文件?
- C++0x將提供何種新的語言特性呢?
- C++11會提供哪些新的標準庫文件呢?
- C++0x努力要達到的目標有哪些?
- 指導標準委員會的具體設計目標是什么?
- 在哪里可以找到標準委員會的報告?
- 從哪里可以獲得有關C++11的學術性和技術性的參考資料?
- 還有哪些地方我可以讀到關于 C++0x的資料?
- 有關于C++11的視頻嗎?
- C++0x難學嗎?
- 標準委員會是如何運行的?
- 誰在標準委員會里?
- 實現者應以什么順序提供C++11特性?
- 將會是C++1x嗎?
- 標準中的"concepts"怎么了?
- 有你不喜歡的C++特性嗎?
- 關于獨立的語言特性的問題
- __cplusplus宏
- alignment(對齊方式)
- 屬性(Attributes)
- atomic_operations
- auto – 從初始化中推斷數據類型
- C99功能特性
- 枚舉類——具有類域和強類型的枚舉
- carries_dependency
- 復制和重新拋出異常
- 常量表達式(constexpr)
- decltype – 推斷表達式的數據類型
- 控制默認函數——默認或者禁用
- 控制默認函數——移動(move)或者復制(copy)
- 委托構造函數(Delegating constructors)
- 并發性動態初始化和析構
- noexcept – 阻止異常的傳播與擴散
- 顯式轉換操作符
- 擴展整型
- 外部模板聲明
- 序列for循環語句
- 返回值類型后置語法
- 類成員的內部初始化
- 繼承的構造函數
- 初始化列表
- 內聯命名空間
- Lambda表達式
- 用作模板參數的局部類型
- long long(長長整數類型)
- 內存模型
- 預防窄轉換
- nullptr——空指針標識
- 對重載(override)的控制: override
- 對重載(override)的控制:final
- POD
- 原生字符串標識
- 右角括號
- 右值引用
- Simple SFINAE rule
- 靜態(編譯期)斷言 — static_assert
- 模板別名(正式的名稱為"template typedef")
- 線程本地化存儲 (thread_local)
- unicode字符
- 統一初始化的語法和語義
- (廣義的)聯合體
- 用戶定義數據標識(User-defined literals)
- 可變參數模板(Variadic Templates)
- 關于標準庫的問題
- abandoning_a_process
- 算法方面的改進
- array
- async()
- atomic_operations
- 條件變量(Condition variables)
- 標準庫中容器方面的改進
- std::function 和 std::bind
- std::forward_list
- std::future和std::promise
- 垃圾回收(應用程序二進制接口)
- 無序容器(unordered containers)
- 鎖(locks)
- metaprogramming(元編程)and type traits
- 互斥
- 隨機數的產生
- 正則表達式(regular expressions)
- 具有作用域的內存分配器
- 共享資源的智能指針——shared_ptr
- smart pointers
- 線程(thread)
- 時間工具程序
- 標準庫中的元組(std::tuple)
- unique_ptr
- weak_ptr
- system error