std::vectorメモリ管理
std::vectorにはreserveというメソッドがあるがどのように動作するのか調べてみた。
struct Info { uint8_t* dex = nullptr; Info(){ dex = new uint8_t; *dex = 7; printf("%p Info() dex %p %d\n", this, dex, *dex); } Info(const Info& obj){ dex = new uint8_t; *dex = *obj.dex; printf("%p Info(const Info&) dex %p %d\n", this, dex, *dex); } #if 0 Info(Info&& obj){ dex = std::move(obj.dex); obj.dex = nullptr; printf("%p Info(Info&&)\n", this); } #endif ~Info(){ if (dex != nullptr){ printf("%p ~Info() dex %p %d\n", this, dex, *dex); } else { printf("%p ~Info() dex %p\n", this, dex); } delete dex; } }; int main(int argc, const char * argv[]) { std::vector<Info> v; v.reserve(2); printf("v.data() %p\n", v.data()); v.push_back(Info()); printf("v[0] %p dex %p %d\n", &v.at(0), v.at(0).dex, *(v.at(0).dex)); while(true){ } return 0; }
v.data() 0x100200170 0x7fff5fbff6b0 Info() dex 0x100103b50 7 0x100200170 Info(const Info&) dex 0x100103b60 7 0x7fff5fbff6b0 ~Info() dex 0x100103b50 7 v[0] 0x100200170 dex 0x100103b60 7
reserveによってアロケートされるのは構造体のメンバ変数のみつまり、uint8_t* dex のポインタサイズ分だけである。
1回目のコンストラクタはpush_backの中のInfo()によるものである。 2回目のコンストラクタはpush_backの中で生成されたInfo()をコピーコンストラクタで 要素の0番目のアドレスの位置にコピーしているところである。 ここでのコンストラクタのアドレスはv_data()のアドレスと同じであることから、メモリのアロケートは 行われずにコンストラクタだけが実行されている。
new expression - cppreference.com
これはここにあるように、placement newと呼ばれるものである。 以下のように既にアロケートされたメモリ領域にTを埋め込み new(ptr) T と呼び出すことで、コンストラクタを実行し、 T*のポインタを得ている。
char* ptr = new char[sizeof(T)]; // allocate memory T* tptr = new(ptr) T; // construct in allocated storage ("place") tptr->~T(); // destruct delete[] ptr; // deallocate memory
さて、ここでついでに#if 0の部分をコメントアウトし、ムーブコンストラクタを定義して再度実行してみよう。
v.data() 0x100103b30 0x7fff5fbff6b0 Info() dex 0x100103b40 7 0x100103b30 Info(Info&&) 0x7fff5fbff6b0 ~Info() dex 0x0 v[0] 0x100103b30 dex 0x100103b40 7
push_backの引数部分は右辺値であるからムーブコンストラクタが呼び出されているのが分かる。 ムーブコンストラクタが未定義の場合、コピーコンストラクタが呼び出されるようだ。