编辑:虽然回答试图解释其原因,但我仍不明白将Union作为派生类比将Union作为类更糟糕的原因。所以,希望获得更具体的答案和原因,我将对此进行悬赏。对已经发布的答案没有冒犯之意,谢谢!
Tony Park提供了一个非常接近真相的答案。C++委员会基本上认为让联合成为C++的强大组成部分不值得付出努力,类似于将数组视为我们必须从C继承但实际上并不想要的遗留物。
联合存在问题:如果我们允许在联合中使用非POD类型,它们如何被构造?当然可以做到,但不一定安全,并且任何考虑都需要委员会资源。最终结果将不尽如人意,因为在理智的语言中真正需要的是辨别联合,而裸的C联合永远无法以与C兼容的方式被提升为辨别联合(至少我能想象到的)。
对于技术问题的详细说明:由于您可以在不失去任何东西的情况下用结构体包装一个仅限于POD成分的联合,因此允许联合作为基础没有任何优势。对于仅限于POD成分的联合组件,显式构造函数只需分配其中一个成分即可,使用位块传输(memcpy)作为编译器生成的复制构造函数(或赋值)也没有问题。
然而,这些联合除了保留它们以便现有的C代码可以被视为有效的C++之外没有足够的用处。这些仅限于POD成分的联合在C ++中是有问题的,因为它们未能保留在C中所具有的重要不变量:任何数据类型都可以用作组件类型。
要使联合有用,我们必须允许构造类型作为成员。这很重要,因为仅在构造函数体本身或任何封闭结构体中分配组件是不可接受的:例如,您无法将字符串分配给未初始化字符串组件。
因此,必须想出一些规则来使用mem-initializer初始化联合组件,例如:
union X { string a; string b; X(string q) : a(q) {} };
现在的问题是:什么是规则?通常,规则是您必须初始化类的每个成员和基类。如果您没有明确地这样做,则默认构造函数将用于其余部分,并且如果未明确初始化任何一个类型,则不具有默认构造函数,则会出现错误[异常:复制构造函数,默认是成员复制构造函数]。
很明显,这个规则对于联合体无法起作用: 规则必须改为:如果联合体至少有一个非POD成员,则必须在构造函数中明确初始化一个成员。在这种情况下,不会生成默认构造函数、复制构造函数、赋值运算符或析构函数,如果这些成员中的任何一个实际上被使用,则必须明确提供它们。
那么现在的问题是:您该如何编写例如复制构造函数?当然,如果您像X窗口事件联合一样设计联合,每个组件中都有辨别标记,则很可能做到并正确实现,但您必须使用放置new操作符来执行此操作,并且您必须打破我上面写的规则——乍一看似乎是正确的规则!
默认构造函数怎么办?如果没有这样的函数,就不能声明未初始化的变量。
还有其他情况下,您可以在外部确定组件并使用放置new来管理联合体,但那不是复制构造函数。事实上,如果您有N个成员,则需要N个构造函数,并且C++存在一个错误的想法,即构造函数使用类名,这会让您缺少名称,并强制您使用幻影类型以允许重载选择正确的构造函数...而对于复制构造函数,由于其签名是固定的,因此无法这样做。
那么,有替代方法吗?可能是的,但不容易想出,而且更难说服100多人在三天密集的会议中考虑它们,因为会议中还涉及其他问题。
遗憾的是委员会没有实施上述规则: 联合体是用于对齐任意数据的强制性工具,手动进行组件的外部管理并不是真的很难,当代码由适当的算法生成时,其是微不足道且完全安全的。换句话说,如果要将C++用作编译器目标语言并仍然生成可读性好、可移植的代码,则该规则是必需的。具有可构造成员的这些联合体具有许多用途,但最重要的用途是表示包含嵌套块的函数的堆栈帧:每个块在结构中具有局部数据,每个结构都是联合组件,不需要任何构造函数或类似的东西,编译器将只使用放置new。联合提供了对齐和大小,以及无需强制转换即可访问组件。
因此,对于您的问题的答案是:您问错了问题。POD-only联合体作为基类没有优势,它们当然不能是派生类,因为那样它们就不会是PODs。要使它们有用,需要一些时间来理解为什么应该遵循C++中其他地方使用的原则:除非尝试使用缺失的位,否则不存在错误。
联合体是一种类型,可以根据已设置的成员使用其任何一个成员 - 只能稍后读取该成员。
当您从一个类型派生时,派生类型继承基本类型 - 派生类型可以在基本类型可以使用的任何地方使用。如果您可以从联合体派生,则派生类可以在任何联合体成员可以使用的地方使用(不是隐式地,而是通过命名成员显式使用),但在这些成员中只有一个成员可以合法访问。问题是哪个成员已设置的数据未存储在联合体中。
为避免这种微妙但危险的矛盾实际上颠覆了类型系统,不允许从联合体派生。
struct V3 {
union {
struct {
float x,y,z;
};
float f[3];
};
};
我认为你在EJP的回答评论中已经得到了答案。
我认为联合体之所以被包含在C++中,仅仅是为了与C保持向后兼容。我猜想,在1970年代的内存空间极小的系统上,联合体似乎是一个好主意。但是随着C++的出现,我想联合体已经变得不那么有用了。
考虑到联合体本身就很危险,而且并不是非常有用,从联合体继承而来的创建错误的巨大新机会可能只会让人觉得这不是一个好主意 :-)
你可以使用C++11的匿名联合特性继承一个联合的数据布局。
#include <cstddef>
template <size_t N,typename T>
struct VecData {
union {
struct {
float x;
float y;
float z;
};
float a[N];
};
};
template <size_t N, typename T>
class Vec : public VecData<N,T> {
//methods..
};
这是我对C++ 03的猜测。
根据$9.5/1,在C++ 03中,联合体不能有虚函数。有意义的派生的整个重点在于能够覆盖派生类中的行为。如果一个联合体不能有虚函数,那么从联合体派生就没有意义。
因此有了这个规则。
[language-lawyer]
用于此目的。 - Prasoon Saurav