Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why union can't be used in Inheritance?

Tags:

I saw below thing in c++ standard (§9.5/1):

A union shall not have base classes. A union shall not be used as a base class.

A union can have member functions (including constructors and destructors), but not virtual (10.3) functions

From above, union can have constructor and destructor as well.

So why is it not allowed in Inheritance ?

EDIT: For answering comments:

  1. If union is allowed as a base class, its data can be used by a derived class. If derived class is interested to use only one member of union, this way can be used for saving memory. I think, this is improper inheritance. Is it better to have union inside derived class in that case ?

  2. If union is allowed as derived class, it can use services of base class. For example, if Union has multiple types of data. As we know, only one type of data can be used. For each type of data, a base class is present to offer services for that particular type. In this case, multiple inheritance can be used to get services of all base classes for all types of data in Union. This also i feel as improper usage of inheritance. But is there any equivalent concept to achieve content in this point?

Just my thoughts...

like image 864
bjskishore123 Avatar asked Sep 01 '10 04:09

bjskishore123


People also ask

Can union have static members?

In C++ union can contain static members which, as in the case of classes, belong to a class and therefore are common to all objects.

Can unions have functions?

A union can have member functions (including constructors and destructors), but not virtual functions. A union cannot have base classes and cannot be used as a base class.

How do unions work in C++?

In C++17 and later, the std::variant class is a type-safe alternative for a union. A union is a user-defined type in which all members share the same memory location. This definition means that at any given time, a union can contain no more than one object from its list of members.

How is a union declared?

Syntax for declaring a union is same as that of declaring a structure except the keyword struct. Note : Size of the union is the the size of its largest field because sufficient number of bytes must be reserved to store the largest sized field. To access the fields of a union, use dot(.)


2 Answers

Here is a simple workaround:

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

By making the union anonymous, it works just as if the base type were actually a union.

like image 182
Ben Voigt Avatar answered Nov 11 '22 16:11

Ben Voigt


(This answer was written for C++03, the situation may have changed since C++11)

I can't imagine any compelling reason for excluding it... more that there's no particularly good reason for including it. Unions just aren't used enough to matter that much, and there are heavy restrictions on the type of members in the union, restrictions that prevent OO practices. Those restrictions - and the burden of manually tracking the particular variable(s) in a union that have validity - mean pretty much the first thing you want to do with a union is encapsulate it in a class, then you can inherit from that anyway. Letting the union be part of the inheritance chain just spreads the pain around.

So, anything that might give casual C++ users the impression that unions could be mixed into their OO-centric code would cause more trouble than good. Unions are basically a hack for memory conservation, fast-but-nasty data reinterpretation and implementing variants, and tend to be an implementation detail rather than an interface one.

You've added a bit to your question with your edit... thoughts follow.

If union is allowed as a base class, its data can be used by a derived class. If derived class is interested to use only one member of union, this way can be used for saving memory. I think, this is improper inheritance. Is it better to have union inside derived class in that case ?

So, we're discussing:

union U { int i; double d; }; struct D : U { }; 

U's data members would either have to be - implicitly or explicitly - public, protected or private. If they're public or protected, then they're not encapsulated, and we're back in the "share the pain" scenario mentioned above. U has less ability to encapsulate them than a class containing a union. If they're private, then they could just as easily be a union data member in a class.

If union is allowed as derived class, it can use services of base class. For example, if Union has multiple types of data. As we know, only one type of data can be used. For each type of data, a base class is present to offer services for that particular type. In this case, multiple inheritance can be used to get services of all base classes for all types of data in Union. This also i feel as improper usage of inheritance. But is there any equivalent concept to achieve content in this point?

Ok, this is where things get weird. So, we've got:

union U : public A, private B { }; 

Firstly, let me be a bit more explicit about something. Unions can't contain complex, encapsulated objects - they're the antithesis of encapsulation. You're pretty much limited to POD data, and can't have non-default constructors etc.. Note I'm talking about the data members of the union, and not the union itself. So, A and B would - if those union-content rules remain - be very limited, and the ability of U to derive not particularly useful.

That leads into the question of why unions can't manage more complex objects in some safe way. Well, how could they do it? Obvious answer is add a hidden enum to say which one is valid, and some rules about which type should be constructed by default, invoking the destructor first when a different enum field is assigned to, etc etc etc.... Maybe they should throw if someone does something to the member that's not currently constructed? Sounds ok?

Well, firstly, the overhead of an enum might not be necessary, as client code might use one member then another in a known-appopriate order. Checks and exceptions in the language itself are a similar overhead... they may be able to be factored down to a single check before multiple uses if left to the client code. In both cases, you'd be paying for some management overhead that only some applications need - a very un-C++ approach.

Ignoring that, unions aren't that simple anyway. Like enums, their use is designed to be flexible and would be hard to communicate to the compiler so it could clean up, check and automate it. You might think "huh? enums are complex?", but each enum is - conceptually generalised - effectively an arbitrary set of independent bitfields of varying width. It's non-trivial to describe which ones are meant to be mutually exclusive or dependent etc.. The compiler doesn't buy into the problem space at all. Similarly, a union may have one or more concurrently legitimate views on the same data, while others are invalid, with subtleties to boot. For example: a union of int64_t, double, and char[4] could always be read as an int64_t or char[4] after being set as a double, but the other way around might read an invalid double and cause undefined behaviour, unless you're re-reading values that came from a double at some earlier time, perhaps in a serialisation/deserialisation library. The compiler doesn't want to buy into the management of that, which is what it would have to do to ensure object members of unions obeyed the promises implicit in their own encapsulation.

You ask "is it better to have union inside derived class?"... no - doesn't generally work as most objects can't be put into a union (see above). You hit this problem whether the union is inside the derived class, or - through the new language features you postulate - the union actually is the derived class.

I do understand your frustration though. In practice, people do sometimes want unions of arbitrary non-trivial objects, so they hack it up the hard and dirty way using reinterpret cast or similar, managing memory alignment of some space big enough for the set of objects they'll support (or - easier but slower - a pointer to them). You'll find this sort of thing in the boost variant and any libraries. But, you can't derive from them... the knowledge about appropriate usage, the extent of safety checks etc. just isn't deducible or expressible in C++. The compiler's not about to do that for you.

like image 25
Tony Delroy Avatar answered Nov 11 '22 14:11

Tony Delroy