# (广义的)联合体
在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