【ruby】fiber使ってますか?【C++】moveコンストラクタ

2019年03月16日

【C++】placement new4

今回はplacement newの解説記事であるが、筆者自身はなるべくこの機能を使うのを避けるようにしている。 プログラムの複雑度が増し、普通のnew演算子を使うよりもメモリ解放がリスキーになるためだ。 もし他の方法で解決できるなら、なるべくそうした方が良い。

さて、C++において普通のnew演算子は「メモリ領域を確保し、オブジェクトを生成する」機能を持っている。 しかし、メモリ領域の確保とオブジェクトの生成を別にしたい事も起こりうる。 そんなときに使うのがplacement newである。 placement newはメモリの確保を行わず、「予め確保されたメモリ領域にオブジェクトを生成し、コンストラクタを呼ぶ」 という働きをする。では、さっそく使い方を見ていこう。

#include <cstdio>
#include <string>
#include <new>

class SampleClass{
public:
    explicit SampleClass(const char* name) : m_name(name){
        printf("%s is constructed. 0x%X\n", m_name.c_str(), this);
    }
    ~SampleClass(){
        printf("%s is destructed. 0x%X\n", m_name.c_str(), this);
    }
private:
    std::string m_name;
    long m_stuff[1024];
};

// 既に作ったオブジェクトの場所に新たなオブジェクトを生成する方法
void test1(){
    printf("test 1\n");
    SampleClass location("A");
    // デストラクタを呼び出してオブジェクトを削除する(メモリ解放は行わない)
    location.~SampleClass();
    printf("instance in 0x%X is being replaced.\n", &location);
    // もともとオブジェクトAがあったところにオブジェクトBを生成
    SampleClass* sc = new(&location) SampleClass("B");
    // もともとAはtest1を抜けると解放されるので、この場合は明示的にデストラクタを呼んではいけない。
}

// メモリだけ確保しておいて後からコンストラクタを呼ぶ使い方
void test2(){
    printf("test 2\n");
    void* allocated_mem = operator new(sizeof(SampleClass));
    SampleClass* psc = reinterpret_cast<SampleClass*>(allocated_mem);
    printf("memory allocated. address = 0x%X\n", psc);

    SampleClass* sc = new(psc) SampleClass("C");
    // デストラクタの手動呼び出しとオブジェクト解放を忘れずに
    sc->~SampleClass();
    operator delete(sc);
}

// デフォルトコンストラクタが使えないときの配列確保
void test3(){
    printf("test 3\n");
    // SampleClass* array = new SampleClass[4];
    SampleClass* array = reinterpret_cast<SampleClass*>(
        operator new(sizeof(SampleClass) * 4));
    for(int i = 0; i < 4; i++){
        new(array + i) SampleClass("array");
    }
    // 必ずデストラクタを明示的に呼ぶこと。
    for(int i = 0; i < 4; i++){
        array[i].~SampleClass();
    }
    // 確保したメモリ領域の解放も忘れずに
    operator delete(array);
}

int main(void) try{
    for(auto i = 0; i < 10000; i++){
        test1();
        test2();
        test3();
    }
    return 0;
}catch(std::exception& e){
    printf("%s\n", e.what());
    return -1;
}

プログラムを実行し、確かにメモリ使用量が増えないことを確認していただきたい。 このように、placement newを使うとコンストラクタの呼び出しに関してかなり自由に行うことができる。 しかし、その分どこでメモリを確保したか?デストラクタは明示的に呼ぶ必要があるか?など考慮しなければならないことが増えて複雑になる。試しにデストラクタを明示的に呼び出している行を削除してメモリ使用量を確認していただきたい。メモリリークにより増え続けることがお分かりいただけると思う。 このような、メモリ解放に関するリスクに見合うメリットがあるかどうかはよく考える必要がある。

例えば、デフォルトコンストラクタが呼び出せずに困っているなら、引数付きのコンストラクタを呼ぶ簡単なヘルパーオブジェクトを作れば良いだけの話だ。 boost::bindやC++11のstd::bindを使えばそうしたヘルパーの実装も楽にできるだろう。 冒頭にも述べたように、やはりよほどの理由がない限り使うべきではないというのが筆者の考えだ。



dormolin at 22:23│Comments(0)C++ 

コメントする

名前
 
  絵文字
 
 
【ruby】fiber使ってますか?【C++】moveコンストラクタ