C++のmoveの意味は本当にそんなに恐ろしくありません

5001 ワード

前言
To move or not to move: that is the question.
  ― Bjarne Stroustrup, 2010
C++11の新しい特性として、move (move semantics)と (rvalue reference)がよく一緒に議論され、ネット上でも多くの分析文章が見られる.しかし、 という新しい概念を使ってmove を解釈すると、実は敷居が高く、知っているようで分からないような気がします.少なくとも私にとってはそうです.そこで本稿では, を用いてmove を解釈することをできるだけ避け,実際のプログラミングにおけるその使い方を説明する.本稿では、C++標準ライブラリの関数を例に挙げて説明します.move に適用されるクラスライブラリをどのように自分で作成するかは、考慮の範囲内ではありません(これは に関連しています).車輪さえ使えない人が良い車輪を作ることはできないと信じています.
1.move意味再考
まず,この語を文法に基づいてmove に分割して考え,まず を説明し,次にmoveを説明する.
1.1.意味vs.文法
もっと簡単に率直に言う.
  • 構文=あるソースコードの書き方
  • 意味=このソースコードはどのように実行します
  • b = a[4];
    //  
    b = *(a + 4);
    

    ここでは同じ意味に対して、2つの異なる文法が使用できます.一般的には前のものを選んでプログラムを書きますが、これは「文法糖」にすぎません.本来は後者の書き方を使えば十分であるが、読解や編纂の観点から後者の書き方が理解の難しさを増しているため、前の書き方(文法と呼ぶべき)が増えている.ここでこの例を挙げるのは、文法と意味の関係が1:1の関係ではないことを説明するためだけである.
    1.2.コピーvs.移動moveに対してcopyである.周知のように、C++の変数はすべて である.いわゆる の変数は、その動作がintタイプと同じであり、典型的には、パラメータとして関数に入力されるには、形式パラメータとしてコピーする必要があり、その変化は元の変数に影響を与えない.ここでは,先ほど述べた文法/意味の関係に戻り,u=tという文法に対して,実際の意味は tの内容を uにコピーすることである.
    1.3.moveの2つの意味
    先ほど述べた の実装は,本質的に複製に依存する.しかし、配列内の要素を2に乗じると、値の意味だけでこのような関数が実現され、結果は次のようになります.
    std::vector twice_vector(std::vector);  //            2     
    
    std::vector v = { /*    */ };
    std::vector w = twice_vector( v ); 
    // v            
    

    上のコードでは、意味のないcopyが何度も発生し、時間を失ってメモリが失われました.もちろん、ここでは「引用タイプを使いましょう.std::vector&を知っていれば、ここでも同様に使用できます.move はまた、スマートポインタmove のような別の場所を使用します.ただし、unique_ptrによって所有権の移動を実現することができ、以下のコードを参照してください.
    boost::unique_ptr p( new int(42) );
    boost::unique_ptr q = p;  //     !(     )
    
    boost::unique_ptr q ← p;  //   move(     '←'    move  ,        )
    assert(*q==42 && p.get()==NULL);
    

    以上のように には2つの意味がある--1極めて低い性能消費で複製を実現する;②所有権の移転
    1.4.C++11のmoveの意味
    ここでいうmove は、上記のmoveの2つの意味の後者であり、コードに訳すと、
    int *p = new int(42);  //   p   42    
    int *q;
    
    //  42     p   q
    q = p;
    p = NULL;
    assert(*q==42 && p==NULL);
    

    したがって、ここでのmove は、moveの値をmove にコピーし、 の値を に付与することである.こんなに簡単なことである以上、なぜ を新しい特性としなければならないのだろうか.理由としては、①プログラマー所有権の移転が徹底していないこと(後のNULL)を避けること、②C++11という意味をより明確にすること、③文法サポート(つまり右値参照)を増やしてコンパイラがコンパイル時に最適化できるようにすること、などが挙げられる.
    2.moveの意味の使用
    ここでは、p=NULLの標準ライブラリを例として説明する.
    2.1.C++11標準ライブラリとmove
    現在のmove標準ライブラリは基本的にC++11のサポートを追加しています.上記のC++11だけでなく、move の実際のstd::unique_ptrC++03の2つの私たちが最もよく知っているクラスライブラリもサポートされています.
    std::string s1 = "apple";
    std::string s2 = "banana";
    
    s1 = std::move(s2);  // s2   s1,  s2             
    // s1=="banana"
    
    std::vector v1;
    std::vector v2 = {1, 2, 3, 4};
    
    v1 = std::move(v2);  // v2   v1
    // v1=={1, 2, 3, 4}
    
    std::string s1 = "apple";
    std::string s2 = "banana";
    
    std::vector<:string> dict;
    
    dict.push_back( s1 );             //     s1       
    // s1 == "apple"
    dict.push_back( std::move(s2) );  //     s2     
    // (  s2             )
    
    std::vector<:string> v;
    v = std::move(dict);  //           
    // v[0]=="apple" && v[1]=="banana"
    

    2.2.移動後の状態?
    非常に正確な答えを見つけるには、実は面倒です.std::stringの標準クラスライブラリの言い方は「依然として有効だが、状態は不明」であり、実際には一般的には空であるが保証されていない.std::vectorを例に挙げて説明する.
    std::string t = "xmas", u;
    u = std::move(t);
    
    // OK: t = "X";           ,      
    // OK: t.size() ;        ,          
    // NG: t[2];          ,        
    

    しかし、あるクラスライブラリについて、C++11が所有権の移動という意味を表し、極めて低いパフォーマンス消費でレプリケーションを実現するのではなく(参照stringの2つの意味)、このクラスライブラリは、転送後の状態が空であることを保証しなければならないか、またはmove を例に挙げなければならない.
    std::unique_ptr p1( int new(42) );
    std::unique_ptr p2;
    
    p2 = std::move(p1); 
    // p1       ,   p1.get()==NULL    
    

    2.3 moveの意味を利用して効率的な関数を作成する1.3の最初に述べた問題に戻り、配列内の要素をunique_ptrに乗算する関数実装を記述する.次に,1.3により効率的に実現できる.
    typedef std::vector IntVec;
    IntVec twice_vector(IntVec a)
    {
      for (auto& e : a)
        e *= 2;
      return std::move(a); 
    }
    
    IntVec v = { /*...*/ };
    IntVec w = twice_vector( std::move(v) );