C++コンパイラ最適化:Copy Elision

8371 ワード

一時的なオブジェクトの不要なコピーを回避するために、C++コンパイラでは、コピー除去という名前の最適化技術が一般的に使用されます.この技術には、少なくとも次の2つの項目が含まれています.
  • 戻り値最適化(RVO)、すなわち、戻り値が占める空間の割り当て場所を呼び出し先から呼び出し先に移行することによってコピー動作を回避する.戻り値最適化には、戻り値が名前付きローカル変数であるか、または名前のない一時オブジェクトであるかを区別する、名前付き戻り値最適化(NRVO)と名前のない戻り値最適化(URVO)が含まれる.
  • の右の値のコピー最適化は、あるクラスタイプの一時オブジェクトが同じタイプの別のオブジェクトにコピーされた場合、その一時オブジェクトを直接利用する方法によってコピー操作を回避する.この最適化は、右(一時的なオブジェクト)のみで使用でき、左の値では使用できません.

  • コンパイラがCopy Ellision最適化テクノロジーを適用しているかどうかをテストする必要がある場合は、次のコード(cppnextから抜粋)を使用します.
    #include <iostream>
    
    struct X
    {
        X() : id(instances++)
        {
            std::cout << "X" << id << ": construct
    "; } X(X const& rhs) : id(instances++) { std::cout << "X" << id << ": <- " << "X" << rhs.id << ": **copy**
    "; ++copies; } // This particular test doesn't exercise assignment, but for // completeness: X& operator=(X const& rhs) { std::cout << "X" << id << ": <- " << "X" << rhs.id << ": assign
    "; } ~X() { std::cout << "X" << id << ": destroy
    "; } unsigned id; static unsigned copies; static unsigned instances; }; unsigned X::copies = 0; unsigned X::instances = 0; #define CHECK_COPIES( stmt, min, max, comment ) \ { \ unsigned const old_copies = X::copies; \ \ std::cout << "
    " comment "
    " #stmt "
    ===========
    "; \ { \ stmt; \ } \ unsigned const n = X::copies - old_copies; \ if (n > max) \ std::cout << "*** max is too low or compiler is buggy ***
    "; \ if (n < min) \ std::cout << "*** min is too high or compiler is buggy ***
    "; \ \ std::cout << "-----------
    " \ << n << "/" << max \ << " possible copies made
    " \ << max - n << "/" << max - min \ << " possible elisions performed

    "; \ \ if (n > min) \ std::cout << "*** " << n - min \ << " possible elisions missed! ***
    "; \ } struct trace { trace(char const* name) : name(name) { std::cout << "->: " << name << "
    "; } ~trace() { std::cout << "<-: " << name << "
    "; } char const* name; }; void sink(X a) { trace t("sink"); } X nrvo_source() { trace t("nrvo_source"); X a; return a; } X urvo_source() { trace t("urvo_source"); return X(); } X identity(X a) { trace t("identity"); return a; } X lvalue_; X& lvalue() { return lvalue_; } typedef X rvalue; int main() { // Double parens prevent "most vexing parse" CHECK_COPIES( X a(( lvalue() )), 1, 1, "Direct initialization from lvalue"); CHECK_COPIES( X a(( rvalue() )), 0, 1, "Direct initialization from rvalue"); CHECK_COPIES( X a = lvalue(), 1, 1, "Copy initialization from lvalue" ); CHECK_COPIES( X a = rvalue(), 0, 1, "Copy initialization from rvalue" ); CHECK_COPIES( sink( lvalue() ), 1, 1, "Pass lvalue by value" ); CHECK_COPIES( sink( rvalue() ), 0, 1, "Pass rvalue by value" ); CHECK_COPIES( nrvo_source(), 0, 1, "Named return value optimization (NRVO)" ); CHECK_COPIES( urvo_source(), 0, 1, "Unnamed return value optimization (URVO)" ); // Just to prove these things compose properly CHECK_COPIES( X a(urvo_source()), 0, 2, "Return value used as ctor arg" ); // Expect to miss one possible elision here CHECK_COPIES( identity( rvalue() ), 0, 2, "Return rvalue passed by value" ); }

    コードの説明
  • lvalue()関数はstruct Xタイプのグローバル変数lvalue_を返します.を参照するため、lvalue()関数は左の値を返し、右の値のコピー最適化をテストするために使用できます.
  • rvalue()関数はstruct Xタイプのコンストラクタによって構築された一時オブジェクトを返します.したがって、rvalue()関数は右の値を返し、右の値のコピー最適化をテストするために使用できます.
  • nrvo_source()関数はstruct Xタイプの局所変数aを返すのでnrvo_source()関数は、名前の戻り値の最適化をテストするために使用できます.
  • urvo_source()関数はstruct Xタイプのコンストラクタで構築された一時オブジェクトを返すのでurvo_source()関数は、無名の戻り値の最適化をテストするために使用できます.
  • 第109110行は、クラスオブジェクトの直接初期化における右値コピー最適化をそれぞれ左右の値でテストする.
  • 第112113行は、クラスオブジェクトコピー初期化における右値コピー最適化をそれぞれ左右の値でテストする.
  • 行目115116行は、通常の関数実パラメータにおける右値コピー最適化をそれぞれ左右の値で試験した.
  • 行目11819行は、それぞれ、名前付き戻り値最適化および名前のない戻り値最適化をテストするために使用される.

  • Windowsプラットフォームの下でgcc 4を使用する.6.1コンパイル実行結果は以下の通りである
    X0: construct
    Direct initialization from lvalue X a(( lvalue() )) =========== X1: <- X0: **copy** X1: destroy ----------- 1/1 possible copies made 0/0 possible elisions performed
    Direct initialization from rvalue X a(( rvalue() )) =========== X2: construct X2: destroy ----------- 0/1 possible copies made 1/1 possible elisions performed
    Copy initialization from lvalue X a = lvalue() =========== X3: <- X0: **copy** X3: destroy ----------- 1/1 possible copies made 0/0 possible elisions performed
    Copy initialization from rvalue X a = rvalue() =========== X4: construct X4: destroy ----------- 0/1 possible copies made 1/1 possible elisions performed
    Pass lvalue by value sink( lvalue() ) =========== X5: <- X0: **copy** ->: sink <-: sink X5: destroy ----------- 1/1 possible copies made 0/0 possible elisions performed
    Pass rvalue by value sink( rvalue() ) =========== X6: construct ->: sink <-: sink X6: destroy ----------- 0/1 possible copies made 1/1 possible elisions performed
    Named return value optimization (NRVO) nrvo_source() =========== ->: nrvo_source X7: construct <-: nrvo_source X7: destroy ----------- 0/1 possible copies made 1/1 possible elisions performed
    Unnamed return value optimization (URVO) urvo_source() =========== ->: urvo_source X8: construct <-: urvo_source X8: destroy ----------- 0/1 possible copies made 1/1 possible elisions performed
    Return value used as ctor arg X a(urvo_source()) =========== ->: urvo_source X9: construct <-: urvo_source X9: destroy ----------- 0/2 possible copies made 2/2 possible elisions performed
    Return rvalue passed by value identity( rvalue() ) =========== X10: construct ->: identity X11: <- X10: **copy** <-: identity X11: destroy X10: destroy ----------- 1/2 possible copies made 1/2 possible elisions performed
    *** 1 possible elisions missed! *** X0: destroy