简体   繁体   中英

C++ - union with nontrivial class member type?

I'm working with a union which has a member that is a class that uses diamond inheritance, but the program encounters a segmentation fault upon assignment to this member.

My suspicion is that I need to add some copy constructors, but after multiple attempts, the correct way to do this still evades me.

I've included a minimal, reproducible example here.

struct Base
{
    Base() : a(0) {}
    Base(int x) : a(x) {}

    int a;
};

struct Derived1 : virtual public Base
{
    Derived1() {}
};

struct Derived2 : virtual public Base
{
    Derived2() {}
};

struct Final : public Derived1, public Derived2
{
    Final() {}
};

union Example
{
    Final value;
    int i;
};

int main()
{
    Example example{ Final() };
    example.i = -1;

    /* Segfault on the line below. 
     * If the above line

       example.i = -1;
     
     * is removed, the segfault is not encountered. */

    example.value = Final();
}

My thanks to anyone who knows how to do this.

Since the Base class is virtual , most likely there are some internal control structures, which become destroyed, when you assign

example.i = -1;

When you recreate value , eg

new(&example.value) Final();
example.value = Final();

before the assignment, the segmentation fault goes away. Although, I wouldn't recommend this hack.


As already mentioned in the comments, a std::variant would be more appropriate, if you have C++17 available. The example would then become

std::variant<Final, int> example{ Final() };
example = -1;
example = Final();

Begin with c++11, unions with members that define their own constructors and/or copy-control members are allowed but When a union has members of built-in type, we can use ordinary assignment to change the value that the union holds, but Not for unions that have members of nontrivial class types. When we switch the union's value to and from a member of class type, we must construct or destroy that member. for example see bellow code

#include <iostream>
#include <new> // for placement new

class Type   // non built in type with constructors
{
private:
    int val;

public:
    Type() : val{0} {    }

    explicit Type(int v) : val{v}  {  }

    Type(const Type &obj) : val{obj.val} {  }

    int getval()  {   return val;  }
};

class unionexample   // I enclose union with class for readability
{
private:
    enum { INT,TYPE } OBJ;

    union
    {
        Type Tval;  // non build in type
        int ival;   // build in type
    };

public:
    unionexample() : ival{0}, OBJ{INT} // default with int
    { }

    unionexample(const unionexample &obj) : OBJ{obj.OBJ}
    {
        switch (obj.OBJ)
        {
        case INT:
            this->ival = obj.ival;
            break;
        case TYPE:
            new (&this->Tval) Type(obj.Tval);
            break;
        }
    }

    unionexample &operator=(int v)
    {
        if (OBJ == TYPE)
        {
            Tval.~Type(); // if it is TYPE destruct it
        }

        ival = v; // assign

        OBJ = INT;

        return *this;
    }

    unionexample &operator=(Type v)
    {
        if (OBJ == TYPE)
        {
            Tval = v;  // if it is alderdy Type just assign
        }

        new (&Tval) Type(v); // else construct

        OBJ = TYPE;

        return *this;
    }

    void print()
    {
        switch (OBJ)
        {
        case INT:
            std::cout << "ival = " << ival << std::endl;
            break;

        case TYPE:
            std::cout << "Tval = " << Tval.getval() << std::endl;
            break;
        }
    }

    ~unionexample()
    {
        if (OBJ == TYPE)  // if it is TYPE we must destruct it
            Tval.~Type();
    }
};

int main()
{
    unionexample ue;
    ue.print();
    ue = Type(1);
    ue.print();
}

output:

ival = 0
Tval = 1

Run Here

In Your case

int main()
{
    Example example{ value : Final() };     // construct with Final

    example.value.~Final();                 // destruct Final

    example.i = -1;                         // assign int built in type
    
    new(&example.value)  Final();           // construct
    
    example.value.~Final();                 // destruct finally
}

If you asked this question other than learning purpose you can use std::variant as in other answer.

Thanks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM