为什么union不能用于继承?

bjs*_*123 18 c++

我在c ++标准(§9.5/ 1)中看到了以下内容:

工会不得有基类.联合不得用作基类.

联合可以具有成员函数(包括构造函数和析构函数),但不具有虚函数(10.3)

从上面,union也可以有构造函数和析构函数.

那么为什么在继承中不允许这样做?

编辑:回答评论:

  1. 如果允许union作为基类,则其数据可以由派生类使用.如果派生类有兴趣只使用union的一个成员,则这种方式可用于保存内存.我认为,这是不正当的继承.在这种情况下,在派生类中使用union更好吗?

  2. 如果允许union作为派生类,它可以使用基类服务.例如,如果Union有多种类型的数据.众所周知,只能使用一种数据.对于每种类型的数据,存在基类以为该特定类型提供服务.在这种情况下,可以使用多重继承来获取Union中所有类型数据的所有基类的服务.这也让我觉得继承的使用不当.但是,在这一点上是否有任何等效的概念来实现内容?

只是我的想法......

Ben*_*igt 18

这是一个简单的解决方法:

struct InheritedUnion
{
    union {
        type1 member1;
        type2 member2;
    };
};

struct InheritsUnion : InheritedUnion
{};
Run Code Online (Sandbox Code Playgroud)

通过使联合匿名,它就像基本类型实际上是一个联合一样.


Ton*_*roy 12

(这个答案是为C++ 03编写的,自C++ 11以来情况可能已经改变)

我无法想象有任何令人信服的理由将其排除在外...更多的是没有特别好的理由将它包括在内.工会的使用不够重要,并且对工会成员的类型有严格的限制,这些限制阻止了OO实践.这些限制 - 以及手动跟踪具有有效性的联合中的特定变量的负担 - 意味着你想要用联盟做的第一件事就是将它封装在一个类中,然后你可以继承它.让联盟成为继承链的一部分只会传播痛苦.

因此,任何可能给临时C++用户带来工会可能混入其以OO为中心的代码的印象都会带来更多麻烦而不是好处.工会基本上是对内存保护,快速但令人讨厌的数据重新解释和实现变体的破解,并且往往是实现细节而不是接口.

你已经添加了一些问题,你的编辑...想法如下.

如果允许union作为基类,则其数据可以由派生类使用.如果派生类有兴趣只使用union的一个成员,则这种方式可用于保存内存.我认为,这是不正当的继承.在这种情况下,在派生类中使用union更好吗?

所以,我们正在讨论:

union U { int i; double d; };
struct D : U { };
Run Code Online (Sandbox Code Playgroud)

U的数据成员要么必须 - 隐式或明确地 - 公开,受保护或私有.如果它们是公开的或受到保护的,那么它们就没有被封装,我们又回到了上面提到的"分担痛苦"的情景中.与包含联合的类相比,U封装它们的能力较弱.如果它们是私有的,那么它们可以很容易地成为类中的联合数据成员.

如果允许union作为派生类,它可以使用基类服务.例如,如果Union有多种类型的数据.众所周知,只能使用一种数据.对于每种类型的数据,存在基类以为该特定类型提供服务.在这种情况下,可以使用多重继承来获取Union中所有类型数据的所有基类的服务.这也让我觉得继承的使用不当.但是,在这一点上是否有任何等效的概念来实现内容?

好吧,这就是事情变得奇怪的地方.所以,我们有:

union U : public A, private B { };
Run Code Online (Sandbox Code Playgroud)

首先,让我对某些事情更加明确.工会不能包含复杂的封装对象 - 它们是封装的对立面.你几乎只限于POD数据,并且不能有非默认的构造函数等.注意我说的是union的数据成员,而不是union本身.因此,A和B将 - 如果那些联合内容规则仍然存在 - 非常有限,并且U的推导能力不是特别有用.

这导致了为什么工会不能以某种安全的方式管理更复杂的对象的问题.那么,他们怎么能这样做呢?明显的答案是添加一个隐藏的枚举来说明哪一个是有效的,并且默认情况下应该构造哪些类型的规则,在分配不同的枚举字段等时首先调用析构函数等等.也许它们应该抛出如果有人对当前没有构建的成员做了什么?听起来不错?

嗯,首先,枚举的开销可能没有必要,因为客户端代码可能使用一个成员,然后以已知的适当顺序使用另一个成员.语言本身的检查和异常是类似的开销......如果留给客户端代码,它们可能在多次使用之前被分解为单个检查.在这两种情况下,您都需要为某些应用程序所需的管理开销付费 - 这是一种非C++方法.

忽略这一点,工会并不是那么简单.与枚举一样,它们的使用设计灵活,很难与编译器通信,因此可以清理,检查和自动化.您可能会认为"嗯?枚举很复杂?",但每个枚举在概念上是一般化的 - 实际上是一组任意宽度的独立位域.描述哪些是互斥或依赖等等是非常重要的.编译器根本不会购买问题空间.类似地,联合可能在同一数据上具有一个或多个并发合法视图,而其他视图则无效,并且具有微妙的引导.例如:在设置为double之后,int64_t,double和char [4]的并集总是可以读取为int64_t或char [4],但另一种方法可能会读取无效的double并导致未定义的行为,除非您在某个更早的时间重新读取来自double的值,可能在序列化/反序列化库中.编译器不想购买管理器,这是为了确保工会的对象成员遵守他们自己封装中隐含的承诺而必须做的事情.

你问"在派生类中使用联合是否更好?"......不 - 通常不起作用,因为大多数对象不能被放入联合(见上文).无论联合是否在派生类中,你都会遇到这个问题,或者 - 通过你假设的新语言特性 - union实际上是派生类.

我确实理解你的挫败感.在实践中,人们有时会想要任意非平凡对象的联合,因此他们使用重新解释强制转换或类似方法来破解它的硬性和肮脏方式,管理一些空间的内存对齐,这些空间足够大以支持它们所支持的对象(或者 - 更容易但更慢 - 指向它们的指针).你会在boost变体和任何库中找到这种东西.但是,你无法从中得出......关于适当使用的知识,安全检查的程度等在C++中是不可推论或可表达的.编译器不打算为你做那件事.