Pebble's Diary

プログラマーの作業メモ

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の引数部分は右辺値であるからムーブコンストラクタが呼び出されているのが分かる。 ムーブコンストラクタが未定義の場合、コピーコンストラクタが呼び出されるようだ。