Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 move constructor for union-like class

Is there a better way to build a move constructor for a union-like class? If I were to have a union-like class like the class in the following code, is there a way to build the class or the move constructor that wouldn't require switch statement like the move constructor in the following code.

class S {
    private:
        enum {CHAR, INT, DOUBLE} type; // tag
        // anonymous union
        union {
            char c;
            int n;
            double d;
        };

    public:
        // constructor if the union were to hold a character
        AS(const char c) {
            this->tag = AS::CHAR;
            this->c = c;
        }
        // constructor if the union were to hold a int
        AS(const int i) {
            this->tag = AS::INT;
            this->n = i;
        }
        // constructor if the union were to hold a double
        AS(const double d) {
            this->tag = AS::DOUBLE;
            this->d = d;
        }

        // Move constructor with switch statement
        S(S &&src) : type(std::move(src.type)) {
            switch(type) {
                case CHAR:
                    this->c = src.c);
                    src.c = 0;
                    break;
                case INT:
                    this->n = src.n;
                    src.n = 0;
                    break;
                case DOUBLE:
                    this->d = src.d;
                    src.d = 0
                    break;
                default:
                    break;
            }
        }
};
like image 760
Daniel Robertson Avatar asked Mar 12 '15 00:03

Daniel Robertson


1 Answers

No, there is no better way. If you want to safely move from a union containing arbitrary types, you must do so from the field of the union that has last been written to (if any). The other answer stating the contrary is wrong, consider an example like

union SomethingLikeThisIsGoingToHappenInPractice {
  std::string what_we_actually_want_to_move;
  char what_we_do_not_care_about[sizeof(std::string)+1];
};

If you use the move constructor of the 'largest' type here you'd have to pick the char array here, despite moving that actually not really doing anything. If the std::string field is set you'd hope to move its internal buffer, which is not going to happen if you look at the char array. Also keep in mind that move semantics is about semantics, not about moving memory. If that was the issue you could just always use memmove and be done with it, no C++11 required.

This isn't even going into the problem of reading from a union member you haven't written to being UB in C++, even for primitive types but especially for class types.

TL;DR if you find yourself in this situation use the solution originally proposed by the OP, not what's in the accepted answer.


PS: Of course if you are just moving a union that only contains things that are trivially moveable, like primitive types, you could just use the default move constructor for unions which does just copy the memory; In that case it's really not worth it to have a move constructor in the first place though, other than for consistencies sake.

like image 145
Cubic Avatar answered Oct 17 '22 10:10

Cubic