Why can't anonymous unions contain members with non-trivial constructors/destructors?
Asked Answered
T

5

5

I may be mistaken, but the basic explanation I've found has been that the union can't initialize because it doesn't know which member's constructor to call. The compiler can not automatically generate a constructor for the union.

Why is the user not allowed to define the unions constructor? This would remove said issue and allow the presence of union members that have a non-trivial constructor/destructor.

Also, why can't a union member have any custom constructors? The previous explanation doesn't stand for custom constructors.

Update 1:

Example:

struct SQuaternion
{
    union
    {
        S3DVector Axis;
        struct
        {
            float X;
            float Y;
            float Z;
        };
    };
    float W;
};

Note: The issue here seems to be that the union is anonymous. As such, how would one name the constructor of the union? It seems impossible to do so, merely because it has no name, and for no other reason. It'd be a terrible reason if it was a simple lexical issue...

Update 2: Simply by wrapping the offending member in an enclosing anonymous structure, the error disappears. I suppose this is the closest thing one can do with an anonymous union. The fact that it ceases to be an issue still seems strange...

Trisyllable answered 9/12, 2010 at 10:9 Comment(3)
Member functions are not allowed in anonymous unions. So what you are looking for is not even possible, to start with.Narcosis
That's more or less what I was looking for or whether there was some alternative manner to handle this.Trisyllable
Anonymous structs aren't officially allowed either.Sponger
C
13

A bigger reason would be: how would the union know which destructor to call. The language itself doesn't track which member is active in a union.

It seems that C++0x will allow non-trivial types in unions, in which case you'll be forced to implement your own constructor(s) and destructor. (The latter is a little unclear from the proposal, it seems that the union destructor will not call any member destructors and the destructor for the right one would have to be invoked manually.)

Catching answered 9/12, 2010 at 10:32 Comment(1)
Aye, that is precisely my point. It just means the compiler can't assume, it seems clear that the user should be required to clarify. That option is seemingly unavailable however. The concept that the user is not even allowed to do so and is forced to depend upon the compiler entirely is problematic.Trisyllable
P
2

It probably would be possible to define such a thing, but that would open such a can of worms that it almost certainly wouldn't be worth it:

  1. Which object to construct?
  2. If you define a union constructor, how does it construct one object vs another?
  3. Do you have to define one constructor per non-pod member?
  4. What is the syntax for assigning to a different object to the one that is currently assigned?
  5. Do you allow one object to be interpreted in the context of another? This is allowed for unions currently, and is a very common use-case (union { char c[4]; int n; } u; u.n=1234; cout << u.c[1];).
  6. How does the compiler know which destructor to call?
  7. How does the compiler know which copy-constructor to call when copying a union?

(Have I left anything out?)

I suspect it just went into the too-hard basket.

Projective answered 9/12, 2010 at 10:46 Comment(2)
1) The one you explicitly construct in your constructor. 6) The one you explicitly destroy in your destructor. 7) The one you explicitly call in your copy-constructor. 2) Don't understand what you mean, please clarify. 3) Why would that be necessary? 4) Same as usual, there would have to be an explicit assignment operator defined as well. 5) If you removed that capacity, then you'd remove part of the inherent design of a union.Trisyllable
@Sion: The questions were mostly rhetorical. I opened with the remark that it probably would be possible.Projective
U
1

I think you're right, unions in C++ are under-featured. They're pretty much a straight copy of unions from C, which means that they don't serve as variant types for C++.

There's no simple way for union in C++ to represent a proper variant type. Consider the following code, if it were legal:

union X {
    int i;
    std::string s;
};

X x;
x.s = "Hello";
x.i = 23;

No amount of constructors or destructors for X is going to ensure that the assignment in the final line calls ~string before storing 23. For the compiler to do it, the union would have to contain some kind of indicator what type is stored. That's why everything must be POD. I don't know the reasons for the differences between named and unnamed unions, though, this applies to both.

Perhaps C++ unions could have been defined to be like C unions if all their members are POD, but to contain this extra information, and call the correct destructors at the correct times, if any member is non-POD. But this isn't the simple change you proposed.

You can somewhat laboriously write a variant type by writing a class which has a value to indicate the type currently stored, and then the constructors, copy assignment operator, and destructor that you would have put in your union, had you been allowed.

Use a char array for storage, placement new for construction/assignment, and a direct call to the correct destructor in your destructor.

Beware of the alignment issue - you need to ensure that your raw storage is adequately aligned for any of the types you place in it. One way to do this is to dynamically allocate it. Another is to put your char array into a union with whatever built-in type has the greatest alignment requirement (if you don't know: all of them).

The only thing that's different in usage, from the union you want, is that instead of public data members int a, float b, string c, you'll have to provide accessors that return a proxy object (possibly a reference to the object itself), that is capable of assigning correctly, which means calling the destructor for the old type first. Then you can write x.i() = 23 instead of x.i = 23.

Or, you can use Boost.Variant.

Ulphi answered 9/12, 2010 at 11:59 Comment(1)
Boost.Variant is closer to a union. Boost.Any is closer to a type-checked void*Catching
N
0

This code seems to work fine for me:

typedef union uAA {
    double dVal;
    int iVal[2];

    uAA() : dVal(3.22) {}
} UAA;

main() {
    UAA rdata;

    printf("Array output: %d %d \nDouble output: %lf \n",
        rdata.iVal[0], rdata.iVal[1], rdata.dVal);
}
Narcosis answered 9/12, 2010 at 10:14 Comment(4)
I suppose I should say an unnamed-union. In which case, I'd have to arrive at the issue that the reason why you can't do this with an unnamed-union is because you can't write a constructor for an unnamed-union, it has no name with which to write the method name. Huh...Trisyllable
@Sion, thx, I had never heard about un-nammed unions... really weird strange concept... not sure I see its interest for the moment...Vogeley
I used the incorrect term, I should have said anonymous union, but the concept is the same. An anonymous union's members are more or less members of the membership enclosing the union.Trisyllable
@Sion, just a question of taste, I see union like a generalized use of reinterpret_cast<>()... so I tend to avoid variant and union as much as I can as dangerous entities that they are ;-) But I'll remmeber it exists thanx.Vogeley
S
0

You've heard right - non-trivial constructors for members suggest the object is meant to encapsulate its data content, and unions remove that encapsulation. Putting a union of simple types into a class is a great way to add encapsulation, and make sure the union content is used in a sensible, safe way.

Re unions having members: from 9.5

A union can have member functions (including constructors and destructors), but not virtual (10.3) functions. A union shall not have base classes. A union shall not be used as a base class.An object of a class with a non-trivial constructor (12.1), a non-trivial copy constructor (12.8), a non-trivial destructor (12.4), or a non-trivial copy assignment operator (13.5.3, 12.8) cannot be a member of a union, nor can an array of such objects

Septilateral answered 9/12, 2010 at 10:23 Comment(3)
Yes, but that's just yet another reiteration of the rule and I get the reason, but why is there no capacity to define an anonymous/unnamed-unions constructors and destructor to invoke the proper member constructors/destructor manually? Does that capacity exist?Trisyllable
@Sion: can't you give the union a name, define the constructor/destructor - why keep the union anonymous? BTW - if you have needs around unions and their clean usage, you should consider boost::variant.Septilateral
Because an anonymous union gives all of its membership to the enclosing membership.Trisyllable

© 2022 - 2024 — McMap. All rights reserved.