Move constructor of unrestricted union crashes with invalid_pointer

463 views Asked by At

An example with unrestricted union class that holds either a map of ints or a vector of ints:

#include <iostream>
#include <string>
#include <vector>
#include <map>

typedef std::vector<int> int_vec; 
typedef std::map<std::string, int> int_map; 

struct Vec {

  bool is_map;

  union {
    int_vec int_vec_val;
    int_map int_map_val;
  };

  // MOVE CONSTRUCTORS
  Vec(int_vec&& val)  : is_map(false), int_vec_val(std::move(val)) {
    std::cout << "move vec" << std::endl;
  }

  Vec(int_map&& val)  : is_map(true),  int_map_val(std::move(val)) {
    std::cout << "move map" << std::endl;
  }

  Vec(Vec&& rhs) : is_map(rhs.is_map) {
    if (is_map) {
      int_map_val = std::move(rhs.int_map_val);
    } else {
      std::cout << "Crashing now ... " << std::endl;
      int_vec_val = std::move(rhs.int_vec_val); // <--- how to do this correctly?
    }
  }

  // DESTRUCTOR
  ~Vec() noexcept {
    if (is_map) {
      int_map_val.~int_map(); 
    } else {
      int_vec_val.~int_vec();      
    }
  }
};

Vec gen_Vec() {
  Vec v1(int_vec({1, 2, 3}));
  return Vec(std::move(v1));
}

int main() {
  Vec v2  = gen_Vec();
}

This code crashes with *** Error in ./tmp2: munmap_chunk(): invalid pointer: 0x0000000000400f6d *** in the Vec(Vec&&) move constructor:

g++ -std=c++11 -O2 tmp2.cpp -lm -o tmp2 && ./tmp2
move vec
Crashing now ... 
*** Error in `./tmp2': munmap_chunk(): invalid pointer: 0x0000000000400f6d ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fba03ab47e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7fba03ac0ae8]
./tmp2[0x400d0d]
./tmp2[0x400a7c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fba03a5d830]
./tmp2[0x400af9]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:11 13240011                           /store/Dropbox/dev/jamr/src/tmp2
00601000-00602000 r--p 00001000 08:11 13240011                           /store/Dropbox/dev/jamr/src/tmp2
00602000-00603000 rw-p 00002000 08:11 13240011                           /store/Dropbox/dev/jamr/src/tmp2
006ce000-00700000 rw-p 00000000 00:00 0                                  [heap]
7fba03734000-7fba0383c000 r-xp 00000000 08:01 1065106                    /lib/x86_64-linux-gnu/libm-2.23.so
7fba0383c000-7fba03a3b000 ---p 00108000 08:01 1065106                    /lib/x86_64-linux-gnu/libm-2.23.so
7fba03a3b000-7fba03a3c000 r--p 00107000 08:01 1065106                    /lib/x86_64-linux-gnu/libm-2.23.so
7fba03a3c000-7fba03a3d000 rw-p 00108000 08:01 1065106                    /lib/x86_64-linux-gnu/libm-2.23.so

My questions are:

  1. What is the correct way to move those vectors and maps?

  2. Is there a way to define Vec(Vec&& rhs) move constructor entirely in the mem initializer list?

Many thanks.

1

There are 1 answers

2
bogdan On BEST ANSWER

The assignment int_vec_val = std::move(rhs.int_vec_val); implies that int_vec_val denotes an existing, live object of type int_vec and you want to change its value by assigning to it. This is not the case: since the members of the anonymous union don't have default member initializers and don't have corresponding mem-initializers either, no initialization is performed on them, so their lifetimes don't start.

This means that none of int_map_val and int_vec_val is alive when the constructor's block is entered. You have to actually construct a new object of the right type in the right place using placement new:

Vec(Vec&& rhs) : is_map(rhs.is_map) {
   if (is_map) {
      std::cout << "move contained map\n";
      new(&int_map_val) int_map(std::move(rhs.int_map_val));
   } else {
      std::cout << "move contained vec\n";
      new(&int_vec_val) int_vec(std::move(rhs.int_vec_val));
   }
}

The answer to your second question is: no, not really, since rhs.is_map is generally only known at run time and the contents of the mem-initializer-list needs to be known at compile time. However, in this case there's no problem with doing the initialization in the constructor's block (and no real benefit to be gained by doing it in the ctor-initializer), since no initialization is performed on the union members by default.