今夜、私は過去数日間取り組んできたいくつかのコードを調べて、移動セマンティクス、特にstd :: moveについて読み始めました。私が正しい道を進んでいて、愚かな仮定をしないことを確実にするためにあなたにプロに尋ねるいくつかの質問があります!
まず:
1)元々、私のコードには大きなベクトルを返す関数がありました:
_template<class T> class MyObject
{
public:
std::vector<T> doSomething() const;
{
std::vector<T> theVector;
// produce/work with a vector right here
return(theVector);
}; // eo doSomething
}; // eo class MyObject
_
「theVector」はこれと「throw-away」で一時的なものであるため、関数を次のように変更しました。
_ std::vector<T>&& doSomething() const;
{
std::vector<T> theVector;
// produce/work with a vector right here
return(static_cast<std::vector<T>&&>(theVector));
}; // eo doSomething
_
これは正しいです?このようにすることの落とし穴はありますか?
2)_std::string
_を返す関数で、moveコンストラクターが自動的に呼び出されることに気づきました。文字列の戻り値(ありがとう、アラゴルン)にデバッグすると、明示的な移動コンストラクターと呼ばれることに気付きました。ベクトルではなく文字列クラス用のものがあるのはなぜですか?
移動セマンティクスを利用するために、この関数に変更を加える必要はありませんでした。
_// below, no need for std::string&& return value?
std::string AnyConverter::toString(const boost::any& _val) const
{
string ret;
// convert here
return(ret); // No need for static_cast<std::string&&> ?
}; // eo toString
_
)最後に、いくつかのパフォーマンステストを実行したかったのですが、std :: moveセマンティクスが原因で得られた驚くほど高速な結果ですか、それともコンパイラ(VS2010)も最適化を実行しましたか?
(簡潔にするために_getMilliseconds()
の実装は省略されています)
_std::vector<int> v;
for(int a(0); a < 1000000; ++a)
v.Push_back(a);
std::vector<int> x;
for(int a(0); a < 1000000; ++a)
x.Push_back(a);
int s1 = _getMilliseconds();
std::vector<int> v2 = v;
int s2 = _getMilliseconds();
std::vector<int> v3 = std::move(x);
int s3 = _getMilliseconds();
int result1 = s2 - s1;
int result2 = s3 - s2;
_
結果は明らかに素晴らしいものでした。標準の割り当てであるresult1は、630ミリ秒かかりました。 2番目の結果は0msでした。これはこれらのことの良いパフォーマンステストですか?
これのいくつかは多くの人にとって明らかなことですが、コードをブレザーに入れる直前にセマンティクスを理解していることを確認したいと思います。
前もって感謝します!
参照は引き続き参照です。同じように、C++ 03ではローカルへの参照を返すことができません(またはUBを取得します)。C++ 0xではできません。死んだオブジェクトへの参照になってしまいます。たまたま右辺値の参照です。だからこれは間違っています:
std::vector<T>&& doSomething() const
{
std::vector<T> local;
return local; // oops
return std::move(local); // also oops
}
2番目に見たものを実行する必要があります。
// okay, return by-value
std::vector<T> doSomething() const
{
std::vector<T> local;
return local; // exactly the same as:
return std::move(local); // move-construct value
}
関数のローカル変数は、戻ったときに一時的なものであるため、コードを変更する必要はありません。戻り値の型は、移動セマンティクスの実装に責任があるものであり、あなたではありません。
使用したいstd::move
toexplicitlyテストのように、正常に実行されない場合は、何かを移動します。 (どちらが問題ないようです。リリースではそれでしたか?ベクターの内容を出力する必要があります。そうしないと、コンパイラーがそれを最適化します。)
右辺値の参照について知りたい場合は、 これを読んでください 。
return(theVector);
theVector
はローカルオブジェクトであるため、これは特別な言語ルールのためにすでに暗黙的に移動しています。セクション12.8のパラグラフ34および35を参照してください。
特定の基準が満たされると、オブジェクトのコピー/移動コンストラクタおよび/またはデストラクタに副作用がある場合でも、実装はクラスオブジェクトのコピー/移動構築を省略できます。このような場合、実装は、省略されたコピー/移動操作のソースとターゲットを、同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は、2つのオブジェクトがあった後の時点で発生します。最適化なしで破棄されました。コピーの省略と呼ばれるこのコピー/移動操作の省略は、次の状況で許可されます(複数のコピーを削除するために組み合わせることができます)。
— クラスの戻り値の型を持つ関数のreturnステートメントで、式が関数の戻り値の型と同じcv-unqualified型の不揮発性自動オブジェクトの名前である場合、copy /自動オブジェクトを関数の戻り値に直接構築することにより、移動操作を省略できます。
[...]
コピー操作の省略基準が満たされ、コピーされるオブジェクトが左辺値で指定されている場合、オブジェクトが右辺値で指定されているかのように、コピーのコンストラクターを選択するためのオーバーロード解決が最初に実行されます =。
std::vector<T>
(by value)、not a std::vector<T>&&
(参照による)。
しかし、なぜ括弧は? return
は関数ではありません:
return theVector;
GManの答えに追加するには、戻り値の型をstd::vector<T>
に変更しても(参照なし、そうでない場合はUBを取得します)、「1)」の戻り値の式を変更してもパフォーマンスは向上しませんが、向上する可能性があります少し悪い。 std::vector
にはmoveコンストラクターがあり、ローカルオブジェクトを返すため、return theVector;
、return static_cast<std::vector<T>&&>(theVector);
、またはreturn std::move(theVector)
のいずれを記述しても、vector
のコピーコンストラクターは呼び出されますnot。最後の2つのケースでは、コンパイラーはmoveコンストラクターを呼び出すことを強制されます。ただし、最初のケースでは、その機能に対してNRVOを実行できる場合は、移動を完全に最適化する自由があります。何らかの理由でNRVOが不可能な場合にのみ、コンパイラーはmoveコンストラクターの呼び出しに頼ります。したがって、x
が返される関数内のローカルの非静的オブジェクトである場合は、return x;
をreturn std::move(x);
に変更しないでください。変更しないと、コンパイラが別の最適化の機会を使用できなくなります。
何かを移動する標準的な方法は、_static_cast
_ではなくstd::move(x)
を使用することです。 AFAIK、名前付き戻り値の最適化は、値ごとにベクトルを返すことで開始される可能性が高いため、移動セマンティクスの前にも十分に実行されます。
パフォーマンステストは、ムーブセマンティクスがパフォーマンスにどのように役立つかを示す良い例です。コピー代入は100万個の要素をコピーする必要があり、ムーブ代入は基本的にベクトルの内部ポインターを交換するだけです。これは簡単なWordの割り当てです。ほんの数サイクル。