Search code examples
c++c++11initializationdestructorunions

Assignment to deleted/uninitialized object


I have a class like this

struct foo {
    bool type;
    union {
        struct { std::string s; };
        struct { std::function<std::string(void)> f; };
    };
};

I need to have assignment operators defined, but when I'm assigning different type I'm basicly assigning to uninitialized field (I know I have to explicitly call destructor first).

My guess is that such assignment is undefined behavior, since I don't know if assignment of string or function doesn't reuse some fields.

How can I do this correctly without invoking any undefined behavior?

I am limited to C++11.


Solution

  • My guess is that such assignment is undefined behavior, since I don't know if assignment of string or function doesn't reuse some fields.

    Good guess! Assignment always has as its precondition that the left-hand side is actually an object of that type. If we don't have an object on the left-hande side, all bets are off.

    How can I do this correctly without invoking any undefined behavior?

    Construction! When you don't have an object, the only way to get an one is to create it. And since we want to create it in a specific location, that's placement new.

    I am not sure why you are wrapping your types in an extra struct, you want to do this:

    union U {
        U() { }
        ~U() { }
    
        std::string s;
        std::function<std::string(void)> f;
    } u;
    

    With that, your assignment operator would be:

    foo& operator=(foo const& rhs) {
        if (type) {
            if (rhs.type) {
                u.s = rhs.u.s;
            } else {
                u.s.~string();
                new (&u.f) std::function<std::string(void)>(rhs.u.f);
            }
        } else {
            if (!rhs.type) {
                u.f = rhs.u.f;
            } else {
                u.f.~function();
                new (&u.s) std::string(rhs.u.s);
            }
        }
    
        return *this;
    }
    

    You'll want to refactor some of that to support move-assignment without lots of duplication, but that's the rough idea.