【C++】placement new

2019年03月26日

【C++】moveコンストラクタ3

今回はC++11のmoveコンストラクタをご紹介しようと思う。 moveコンストラクタは保持している内容を相手に渡すという動作を期待するときに使う。 具体的には、ポインタで指している内容を相手に渡し、自分の内容はnullにするような動作である。

C++11では非推奨になってしまったが、以前のC++に所有権移行型のstd::auto_ptrがあったのを覚えているだろうか? std::auto_ptrは代入で所有権が移動してしまうという動作がやっかいだった。 a = bとかいてbの内容が書き換わってしまうと普通はびっくりすると思う。 C++11のmoveというのはこの所有権移行動作を明示化し、びっくりするのを避けるというだけの事である。さっそく例を見ていこう。

    #include <cstdio>
    #include <memory>
    #include <stdexcept>
    #include <vector>
    
    class SampleClass{
    public:
        explicit SampleClass(int value) : m_value(new int(value)){
            printf("value = %d, [%p] constructed.\n", *m_value, this);
        }
    
        ~SampleClass(){
            if(m_value){
                printf("value = %d, [%p] destructed.\n", *m_value, this);
            }else{
                printf("value = NULL, [%p] destructed.\n", this);
            }
        }
    
        SampleClass(const SampleClass& c) : m_value(new int(*c.m_value)){       
            printf("value = %d, [%p] copied.\n", *m_value, this);
        }
    
        // moveコンストラクタは所有している中身を相手に移す
        SampleClass(SampleClass&& c) noexcept : m_value(std::move(c.m_value)){
            printf("value = %d, [%p] moved to [%p].\n", *m_value, &c, this);
        }
    
        int value() const {
            if(!m_value){
                char buffer[80];
                snprintf(buffer, sizeof(buffer), 
                    "value = NULL, [%p]\nError! do not use value after move.", this);
                throw std::runtime_error(buffer);
            }
            return *m_value;
        }
    
    private:
        std::unique_ptr<const int> m_value;
        SampleClass();
        SampleClass& operator=(const SampleClass&);
    };
    
    int main(void){
        auto a = SampleClass(100);
        printf("a = %d\n", a.value());
    
        // std::moveを使うとmoveコンストラクタを呼ぶことができる
        // aは内容をbに渡してしまう(自身はnullになる)
        auto b = std::move(a);
        printf("b = %d\n", b.value());
    
        // 普通はmoveしてしまうと二度とアクセスできない
        try{
            printf("a = %d\n", a.value());
        }catch(std::exception& e){
            printf("%s\n", e.what());
        }
    
        // moveコンストラクタを定義しておけばSTLコンテナへの挿入にも使われる
        std::vector<SampleClass> v;
        v.push_back(SampleClass(200));
        printf("v[0] = %d\n", v[0].value());
    
        return 0;
    }

こんな感じで、&&を付けたコンストラクタを定義しておけばstd::moveやSTLコンテナへの挿入で 所有権を移動させるmoveコンストラクタが使われる。 明示的にstd::moveするか&&を付けた引数で受けないと、moveコンストラクタは使われないので安全である。

さて、ここまで紹介してきたmoveコンストラクタだが、自作クラスにmoveコンストラクタを 実装する機会はそれほど無いかもしれない。どちらかというとstd::unique_ptrなどを介して 知らず知らずのうちにお世話になる機会の方が多いと思う。別に右辺値参照やら難しい言葉を覚えなくても 恩恵を受けることはできる。moveは地味ながら役に立つ存在なのである。



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

コメントする

名前
 
  絵文字
 
 
【C++】placement new