托尼帕克给出了一个非常接近事实的答案。C++ 委员会基本上认为不值得努力使联合成为 C++ 的强大部分,类似于将数组视为我们必须从 C 继承但并不真正想要的遗留东西。
联合存在问题:如果我们在联合中允许非 POD 类型,它们是如何构造的?当然可以,但不一定安全,任何考虑都需要委员会资源。最终的结果将不尽如人意,因为在理智的语言中真正需要的是可区分联合,而裸 C 联合永远不能以与 C 兼容的方式提升为可区分联合(无论如何我可以想象)。
详细说明技术问题:由于您可以将仅 POD 组件的联合包装在结构中而不会丢失任何内容,因此允许联合作为基础没有任何优势。对于仅 POD 的联合组件,显式构造函数只需分配其中一个组件,也不存在编译器生成的复制构造函数(或赋值)使用 bitblit (memcpy) 的问题。
然而,除了保留它们以便现有的 C 代码可以被认为是有效的 C++ 之外,这样的联合并没有足够的用处。这些仅 POD 的联合在 C++ 中被破坏,因为它们未能保留它们在 C 中拥有的重要不变量:任何数据类型都可以用作组件类型。
为了使联合有用,我们必须允许可构造类型作为成员。这很重要,因为仅在构造函数主体(联合本身或任何封闭结构)中分配组件是不可接受的:例如,您不能将字符串分配给未初始化的字符串组件。
它遵循必须发明一些使用 mem-initialisers 初始化联合组件的规则,例如:
union X { string a; string b; X(string q) : a(q) {} };
但现在的问题是:规则是什么?通常规则是你必须初始化一个类的每个成员和基类,如果你没有显式这样做,默认构造函数用于其余部分,如果一个没有显式初始化的类型没有默认构造函数,它是一个错误【异常:拷贝构造函数,默认是成员拷贝构造函数】。
显然,这条规则不适用于联合:规则必须改为:如果联合至少有一个非 POD 成员,则必须在构造函数中明确初始化一个成员。在这种情况下,不会生成默认构造函数、复制构造函数、赋值运算符或析构函数,如果实际使用了这些成员中的任何一个,则必须显式提供它们。
所以现在问题变成了:你将如何编写一个拷贝构造函数?当然,如果您按照 X-Windows 事件联合的设计方式来设计联合,那么当然很有可能做到并做得对:每个组件中都有判别标记,但是您必须使用放置运算符 new 来做到这一点,你将不得不打破我上面写的乍一看是正确的规则!
默认构造函数呢?如果您没有其中之一,则不能声明未初始化的变量。
在其他情况下,您可以在外部确定组件并使用placement new 在外部管理联合,但这不是复制构造函数。事实是,如果您有 N 个组件,则需要 N 个构造函数,而 C++ 有一个错误的想法,即构造函数使用类名,这使您的名称相当短,并迫使您使用幻像类型以允许重载选择正确的构造函数..你不能为复制构造函数这样做,因为它的签名是固定的。
好的,那么有替代品吗?可能,是的,但它们并不是那么容易梦想成真,而且更难让 100 多人相信,在充斥着其他问题的三天会议中考虑值得考虑。
遗憾的是委员会没有实施上述规则:联合是强制对齐任意数据和组件的外部管理并不是真的很难手动完成,并且当代码由合适的算法生成时微不足道且完全安全,换句话说,该规则是强制性的如果您想使用 C++ 作为编译器目标语言并仍然生成可读、可移植的代码。这种具有可构造成员的联合有很多用途,但最重要的是表示包含嵌套块的函数的堆栈帧:每个块在结构中都有本地数据,每个结构都是一个联合组件,不需要任何构造函数或者这样,编译器将只使用placement new。联合提供对齐和尺寸,以及无铸造组件访问。[而且没有其他符合要求的方法来获得正确的对齐方式!]
因此,您的问题的答案是:您问错了问题。仅 POD 的联合作为基础没有任何优势,而且它们当然不能是派生类,因为那样它们就不是 POD。为了使它们有用,需要一些时间来理解为什么应该遵循 C++ 中其他地方使用的原则:丢失位不是错误,除非您尝试使用它们。