Is a Union Member's Destructor Called

Is a Union Member's Destructor Called

In your example that you provided str will not be destructed. The standard states in [class.union]/2

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. If a union contains a non-static data member of reference type the program is ill-formed. At most one non-static data member of a union may have a brace-or-equal-initializer . [ Note: If any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. — end note ]

emphasis mine

So since both str and vec have special member functions that are not trivial you will need to provide them for the union yourself.

Do note that as per bogdan's comments below the empty destructor is not enough. In [class.union]/8 we have

[...]If X is a union its variant members are the non-static data members;[...]

So all members of this union are variants. Then if we look at [class.dtor]/8 we have

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members[...]

So the destructor will not automatically destroy the members of the union as they are variants.

You could make a tagged union like kennytm does here

struct TU {
int type;
union {
int i;
float f;
std::string s;
} u;

TU(const TU& tu) : type(tu.type) {
switch (tu.type) {
case TU_STRING: new(&u.s)(tu.u.s); break;
case TU_INT: u.i = tu.u.i; break;
case TU_FLOAT: u.f = tu.u.f; break;
}
}
~TU() {
if (tu.type == TU_STRING)
u.s.~string();
}
...
};

Which ensures the correct member is destroyed or just use a std::variant or boost::variant

Will the members of a union member call their own destructors in this scenario?

Execution of a destructor is defined to execute the body of the destructor function, and then also execute calls to destructors of members and bases . The pseudo-destructor call executes the destructor.

So yes, the pseudo-destructor call will correctly destroy pixels in this case.

Destructor of union member seems to be called automatically

In

S(int type) : type(type) {
if (type == 0) {
u.c = C();
} else {
u.b = B();
}
}

Since you are in the body of the constrcutor, u.c = C(); is not initialization but is instead assigment. That means you see the constructor called for C(), and then at the end of the expression the first destructor call is called to destroy that temporary. We can see this by adding

C& operator=(const C&) { std::cout << "operator=(const C&)\n"; return *this; }

to C which changes the output to

C Ctor
operator=(const C&)
C Dtor
C Dtor

Then the second destructor call is when s goes out of scope in main and its destructor is ran.


Do note that as is, the code has undefined behavior. Unions do not activate a member in the constructor user provided constructor you wrote so when you do

u.c = C();

you are assigning to an object that is not yet alive. You can't modify an object that isn't alive.

How does C++ union know the type stored in it and which destructor to call?

Short answer: It doesn't.

If you add a copy-constructor to no you will see that there are actually three no objects being created, but only two are destructed.

First you create the object n. Then when you pass it by value to the u constructor, it is copied once into the _n argument. That _n object is then copied into the uu.n member.

The destructions are of the _n argument in the u constructor, and the n object in the main function.


Here's your program with some slight modification to add the copy-constructor and to keep track of the no objects:

#include <iostream>

struct yes{
yes(){std::cout<<"yes-c"<<std::endl;}
~yes(){std::cout<<"yes-d"<<std::endl;}
};
struct no{
no(){std::cout<<"no-c : "<<n<<std::endl;}
no(no const& o)
: n(o.n + 1)
{
std::cout << "no-cc : " << o.n << " -> " << n << '\n';
}

~no(){std::cout<<"no-d : "<<n<<std::endl;}
int n = 0;
};
struct u{
union{
yes y;
no n;
};
u(yes _y):y(_y){}
u(no _n):n(_n){}
~u(){}
};

int main()
{
yes y;
no n;
{
u uu(n);
}
}

Without optimizations or copy-elision this will create the output


yes-c
no-c : 0
no-cc : 0 -> 1
no-cc : 1 -> 2
no-d : 1
no-d : 0
yes-d

The output no-c : 0 is for the creation of the n object in the main function.

The output no-cc : 0 -> 1 is for the copying into the u constructor argument _n.

The output no-cc : 1 -> 2 is for the copying of the argument _n into the unions n object.

The output no-d : 1 is the destruction of the _n argument.

The output no-d : 0 is the destruction of the n object in the main function.

How to write destructor for union-like class

This grouping (union + enum value for discriminating type) is called a discriminated union.

It will be up to you to call any construction/destruction, because the union itself cannot (if it could, it would also be able to discriminate for initialized/non-initialized types within the union, and you would not need the enum).

Code:

class LuaVariant // no public access to the raw union
{
public:
LuaVariant() : type(VARIANT_NONE) { }
~LuaVariant() { destroy_value(); }

void text(std::string value) // here's a setter example
{
using std::string;
destroy_value();
type = VARIANT_TEXT;
new (&value.text) string{ std::move(value) };
}
private:

void destroy_value()
{
using std::string;
switch(type)
{
case VARIANT_TEXT:
(&value.text)->string::~string();
break;
case VARIANT_POSITION:
(&value.pos)->Position::~Position();
break;
case VARIANT_NUMBER:
value.number = 0;
break;
default:
break;
}
}

LuaVariantType_t type;
union {
std::string text;
Position pos;
uint32_t number;
} value;
};

Do you always have to declare a default constructor and destructor for unions containing members with user-defined default constructor?

It seems silly to write an empty constructor and destructor for ...

Not only silly, but actually wrong.

Why [does the compiler doesn't generate one and you need to]?

A union doesn't know which of its members is active. As such if at least one of its members has a non-trivial special method the union can't generate that special method because it doesn't know on which member to call that special method.

And now we come to your case. You do need to write a special method, but having it empty achieves nothing. You need to write a special method that correctly delegates to the active member. For instance inside foo you can have a data member (an enum or an index) which tells you which union member is active. And in your union special method you check that index and call the special method on the active member of the union.

Or you can forget about all this and just use the type in the standard library that not only has everything set up for you, but also is type safe: std::variant.

Member of Union has User-Defined Constructor

In C++98/03 the C++ standard stated in 9.5

[...]If a POD-union contains several POD-structs that share a common initial sequence (9.2), and if an object of this POD-union type contains one of the POD-structs, it is permitted to inspect the common initial sequence of any of POD-struct members;[...]

And this was changed in C++11 to

[...]If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2), and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members;[...]

So before C++11 you could only use a POD type inside a union which means MSVS 2008 is giving you the right error. In order to use the new type of union you need to get the version of MSVS that supports that change. From this MSDN article we can see under the section for Unrestricted unions that that change was not made until version 2015.

You are either going to have to upgrade or change the class to be a POD type

How to delete a string object which is a member variable of a C++ union?

A trivial constructor/destructor is one that does nothing. Essentially, if your destructor would look like

~Type() {}

then it is trival. std::string's destructor is not empty like that. It has to clean up any memory that the string may have allocated.

So, since a union's destructor does nothing, but std::string needs to do something (otherwise you get a memory leak) you have to supply a destructor for the union that calls the destructor for the string so that it gets cleaned up correctly.



Related Topics



Leave a reply



Submit