C++: union that contains class it belongs to

285 views Asked by At

While this code is compiled without any error, I doubt that it will work as expected. Is such nesting allowed? I cannot use boost and c++17.

class Node;

typedef struct Value {      
    ValueTag type;

    union {         
        std::int32_t                          integerValue;
        std::float_t                          floatValue;
        bool                                  boolValue;
        std::vector<Node>                     arrayValue;
        std::unordered_map<std::string, Node> dictionaryValue;
    };          
} Value;

class Node {                
private:        
    Value m_value;          
public:                 
    virtual ~Node();    
};
2

There are 2 answers

6
jonas_toth On

Edit My original answer was incorrect/misleading, plz ignore it. for the reason see the comments below. conside std::variant or boost::variant anyway :)

Non PlainOldData(POD) are unsafe in c-unions.

Especially for this proplem std::variant came into the c++17 standard. You could use boost::variant for earlier versions of C++.

The problem is, that union does not know anything about constructors and destructors. Therefore it is not possible to do more general things with elements of a union, that would be necessary (e.g. dynamic memory allocation)

0
Barry On

I doubt that it will work as expected.

You are correct. It won't. First, it won't actually compile if you ever try to construct a Node. You'll get something like:

prog.cc:26:10: error: call to implicitly-deleted default constructor of 'Node'
    Node n;
         ^

That's because in a union, any non-trivial operations are implicitly deleted. You will have to define ~Value() to do the right thing. Which, assuming type is the index of which element in the union we actually are, to switch on that and call the appropriate destructor. And then do the same thing for copying and moving.

That said, the nesting of incomplete types is also not ok. vector is allowed to have an incomplete type as long as it's complete before first use. But unordered_map does not have this allowance. You will have to wrap the value type in something else - like unique_ptr<Node> or shared_ptr<Node>.

What you have in Value is a common pattern that has a million different names: discriminated union, sum type, or, perhaps most commonly, variant. Instead of reinventing the wheel, I suggest you instead use std::variant (if you're using a recent enough compiler to support such a thing) or boost::variant (otherwise). With that, you have:

using Value = variant<int32_t, float, bool,
    std::vector<Node>, std::unordered_map<std::string, Node>>;

and this type is already destructible, copyable, movable, and visitable for you.