Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a Union Member's Destructor Called

C++11 allowed the use of standard layout types in a union: Member of Union has User-Defined Constructor

My question then is: Am I guaranteed the custom destructor will be called, when the union goes out of scope?

My understanding is that we must manually destroy and construct when switching: http://en.cppreference.com/w/cpp/language/union#Explanation

But what about an example like this:

{     union S { string str;               vector<int> vec;               ~S() {} } s = { "Hello, world"s }; } 

When s goes out of scope, have I leaked the memory the string allocated on the heap because I did not call string's destructor?

like image 353
Jonathan Mee Avatar asked Oct 18 '16 11:10

Jonathan Mee


People also ask

Can a union have member 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. A union cannot have non-static data members of reference types.

How do you call a destructor?

A destructor is called for a class object when that object passes out of scope or is explicitly deleted. A destructor is a member function with the same name as its class prefixed by a ~ (tilde). For example: class X { public: // Constructor for class X X(); // Destructor for class X ~X(); };

Why destructors are called in reverse order?

The destructors are called in exactly the reverse order of the constructors – this is important because of potential dependencies (in the derived-class constructor or destructor, you must be able to assume that the base-class subobject is still available for use, and has already been constructed – or not destroyed yet) ...

Is destructor called automatically in C++?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete .


2 Answers

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

like image 142
NathanOliver Avatar answered Sep 22 '22 11:09

NathanOliver


Your example won't compile. Unions have, by default, a deleted destructor. Because of course, what destructor should be called? Surely you can't call both. And nowhere is any information stored about which member was actually constructed. It's up to you to provide a proper destructor.

Here's the output of GCC when trying to compile your code snippet:

In function ‘int main()’: error: use of deleted function ‘main()::<anonymous union>::~<constructor>()’        vector<int> vec; } s = { "Hello, world"s };                                                 ^  note: ‘main()::<anonymous union>::~<constructor>()’ is implicitly deleted because the default definition would be ill-formed:       union { string str;             ^ 
like image 37
G. Sliepen Avatar answered Sep 21 '22 11:09

G. Sliepen