Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

unions

Working with a union of two classes, it appears in this simple example that the union remembers the last class stored in it and calls the correct destructor for that object:

#include <iostream>

using std::cout;
using std::endl;

struct yes{
    yes(){cout<<"yes-c"<<endl;}
    ~yes(){cout<<"yes-d"<<endl;}
};
struct no{
    no(){cout<<"no-c"<<endl;}
    ~no(){cout<<"no-d"<<endl;}
};
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);
    }

    return 0;
}

Output:

yes-c
no-c
no-d
no-d
yes-d

So the uu will call the correct destructor ~no() for the union, as if it records the type when the union is constructed. How does this work?

like image 225
Detective King Avatar asked Dec 30 '25 04:12

Detective King


2 Answers

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.

like image 107
Some programmer dude Avatar answered Dec 31 '25 18:12

Some programmer dude


unions never "remember" which field is active.

Your code outputs:

yes-c
no-c
no-d
no-d
yes-d

You seem to interpret the 3rd line (no-d) as a destructor call for a union field of your class, but that's not what happens here. ~u() isn't going to call destructors for y nor n.

You pass parameters to your constructors by value, thus this line is a destructor call for the parameter of the constructor.

like image 29
HolyBlackCat Avatar answered Dec 31 '25 16:12

HolyBlackCat