C ++ 03コンパイラをC ++ 11として再コンパイルすると、実装の品質に実質的に関係のない無限のパフォーマンスの向上を引き起こす可能性がある5つの一般的なカテゴリを認識しています。これらはすべて移動セマンティクスのバリエーションです。
std::vector
再割り当て
struct bar{
std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03
毎回foo
のバッファは、それがすべてのコピーC ++ 03に再割り当てされるvector
中をbar
。
C ++ 11では、代わりにbar::data
s を移動しますが、これは基本的に無料です。
この場合、これはstd
コンテナ内の最適化に依存していvector
ます。以下のすべてのケースで、std
コンテナーを使用するのはmove
、コンパイラーをアップグレードすると、C ++ 11で「自動的に」効率的なセマンティクスを持つC ++オブジェクトだからです。std
コンテナーを含む、それをブロックしないオブジェクトも、自動改善されたmove
コンストラクターを継承します。
NRVO障害
戻り値の最適化という名前のNRVOが失敗すると、C ++ 03ではコピーにフォールバックし、C ++ 11では移動にフォールバックします。NRVOの失敗は簡単です。
std::vector<int> foo(int count){
std::vector<int> v; // oops
if (count<=0) return std::vector<int>();
v.reserve(count);
for(int i=0;i<count;++i)
v.push_back(i);
return v;
}
あるいは:
std::vector<int> foo(bool which) {
std::vector<int> a, b;
// do work, filling a and b, using the other for calculations
if (which)
return a;
else
return b;
}
戻り値と、関数内の2つの異なる値の3つの値があります。Elisionを使用すると、関数内の値を戻り値と「マージ」できますが、相互にマージすることはできません。両方をマージせずに戻り値とマージすることはできません。
基本的な問題は、NRVOの省略は脆弱であり、return
サイトの近くにない変更を含むコードでは、その場所で突然診断が出力されずに大幅なパフォーマンス低下が発生する可能性があることです。ほとんどのNRVO失敗の場合、C ++ 11はで終わりmove
、C ++ 03はコピーで終わります。
関数の引数を返す
エリシオンもここでは不可能です。
std::set<int> func(std::set<int> in){
return in;
}
C ++ 11ではこれは安価です。C++ 03ではコピーを回避する方法はありません。関数の引数は、戻り値で省略できません。これは、パラメーターの有効期間と場所、および戻り値が呼び出し元のコードによって管理されるためです。
ただし、C ++ 11は一方から他方へ移動できます。(おもちゃの少ない例では、何かがに行われる可能性がありますset
)。
push_back
または insert
最後に、コンテナーへの省略は発生しません。ただし、C ++ 11は、コピーを保存する右辺値移動挿入演算子をオーバーロードします。
struct whatever {
std::string data;
int count;
whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );
C ++ 03では一時ファイルwhatever
が作成され、それがベクターにコピーされv
ます。2つのstd::string
バッファーが割り当てられ、それぞれが同じデータを持ち、1つは破棄されます。
C ++ 11では一時ファイルwhatever
が作成されます。次に、whatever&&
push_back
オーバーロードはmove
その一時的なものをベクターに入れv
ます。1つのstd::string
バッファーが割り当てられ、ベクターに移動されます。空std::string
は破棄されます。
割り当て
以下の@ Jarod42の答えから盗まれた。
割り当てではエリシオンは発生しませんが、移動元は発生します。
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
ここでsome_function
は、除外する候補を返しますが、オブジェクトを直接作成するために使用されないため、除外することはできません。C ++ 03では、上記の結果として一時ファイルの内容がにコピーされsome_value
ます。C ++ 11では、some_value
基本的に無料のに移行されています。
上記の効果を十分に発揮するには、moveコンストラクターと割り当てを合成するコンパイラーが必要です。
MSVC 2013はstd
コンテナーにmoveコンストラクターを実装しますが、型にmoveコンストラクターを統合しません。
そのstd::vector
ため、sなどを含む型は、MSVC2013ではそのような改善は得られませんが、MSVC2015ではそれらの改善が開始されます。
clangとgccには、暗黙のmoveコンストラクターが実装されてから長い間あります。Intelの2013コンパイラーは、渡した場合、移動コンストラクターの暗黙的な生成をサポートします-Qoption,cpp,--gen_move_operations
(MSVC2013との互換性を維持するために、デフォルトでは実行しません)。