C++ 11/14は、単にC++ 98コードをコンパイルする場合でも、パフォーマンスを向上できると言われています。場合によっては、右辺値コンストラクターが自動的に生成されるか、現在STLの一部であるため、正当化は通常、移動セマンティクスのラインに沿っています。今、私はこれらのケースが以前に実際にRVOまたは類似のコンパイラ最適化によってすでに処理されていたかどうか疑問に思っています。
私の質問は、新しい言語機能をサポートするコンパイラーを使用することで、修正なしでより高速に実行されるC++ 98コードの実際の例を提供できるかどうかです。標準準拠のコンパイラはコピーの省略を行う必要がないことを理解しており、そのため、移動セマンティクスによって速度が向上する可能性がありますが、そうでない場合は病理学的なケースが少ないことを確認したいと思います。
編集:ちょうど明確にするために、新しいコンパイラが古いコンパイラよりも速いかどうかを尋ねているのではなく、コンパイラフラグに-std = c ++ 14を追加するコードがあれば、それは高速に実行されます(コピーは避けますが、移動セマンティクス以外の何かを考え出すことができます、私も興味があるでしょう)
C++ 11としてC++ 03コンパイラを再コンパイルすると、実際には実装の品質とは無関係の無制限のパフォーマンスが向上する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
コンテナの使用は、コンパイラをアップグレードするときにC++ 11で「自動的に」効率的なmove
セマンティクスを持つC++オブジェクトであるためです。 std
コンテナを含むオブジェクトをブロックしないオブジェクトも、自動改良されたmove
コンストラクターを継承します。
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
sを一時的にベクトルv
にオーバーロードします。 1つのstd::string
バッファーが割り当てられ、ベクターに移動されます。空のstd::string
は破棄されます。
以下の@ Jarod42の回答から盗まれました。
割り当てでは省略は発生しませんが、移動元からは発生します。
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
here some_function
は、除外する候補を返しますが、オブジェクトを直接構築するためには使用されないため、省略できません。 C++ 03では、上記の結果、一時の内容がsome_value
にコピーされます。 C++ 11では、some_value
に移動されますが、これは基本的に無料です。
上記のすべての効果を得るには、移動コンストラクタと割り当てを合成するコンパイラが必要です。
MSVC 2013は、std
コンテナーに移動コンストラクターを実装しますが、型の移動コンストラクターを合成しません。
そのため、std::vector
sなどを含む型は、MSVC2013ではそのような改善は受けませんが、MSVC2015ではそれらの改善が始まります。
clangとgccには、暗黙の移動コンストラクターが実装されてから長い間あります。 Intelの2013コンパイラは、-Qoption,cpp,--gen_move_operations
を渡すと、移動コンストラクターの暗黙的な生成をサポートします(MSVC2013との互換性を保つために、デフォルトでは実行しません)。
次のようなものがある場合:
std::vector<int> foo(); // function declaration.
std::vector<int> v;
// some code
v = foo();
C++ 03でコピーを取得しましたが、C++ 11で移動割り当てを取得しました。その場合、無料で最適化できます。