2017-03-28 11 views
7

次のコードを考えてみてください:std :: move local stack variablesは可能ですか?

struct MyStruct 
{ 
    int iInteger; 
    string strString; 
}; 

void MyFunc(vector<MyStruct>& vecStructs) 
{ 
    MyStruct NewStruct = { 8, "Hello" }; 
    vecStructs.push_back(std::move(NewStruct)); 
} 

int main() 
{ 
    vector<MyStruct> vecStructs; 
    MyFunc(vecStructs); 
} 

なぜこの仕事はしますか?

MyFuncが呼び出された瞬間に、リターンアドレスは現在のスレッドのスタックに配置する必要があります。今度はNewStructオブジェクトを作成して作成します。これはスタックにも配置する必要があります。 std :: moveを使用して、コンパイラに、私はもうNewStruct参照を使用する予定はないことを伝えます。彼は記憶を盗むことができます。 (push_back関数は移動セマンティクスを持つ関数です)

しかし、関数が返ってくると、NewStructがスコープから外れます。たとえコンパイラがメモリからスタックを取り除いていなくても、スタックから元々存在していた構造が占有していたとしても、少なくとも以前に格納されたリターンアドレスを削除する必要があります。

これは断片化されたスタックにつながり、将来の割り当ては "移動"されたメモリを上書きします。

誰かが私にこのことを説明できますか?


EDIT:すべての まず:あなたの答えをありがとうございました。 しかし、私が学んだこと、私はまだ理解できないから、私はそれが動作することを期待のように動作しません、次の理由:

struct MyStruct 
{ 
    int iInteger; 
    string strString; 
    string strString2; 
}; 

void MyFunc(vector<MyStruct>& vecStructs) 
{ 
    MyStruct oNewStruct = { 8, "Hello", "Definetly more than 16 characters" }; 
    vecStructs.push_back(std::move(oNewStruct)); 

    // At this point, oNewStruct.String2 should be "", because its memory was stolen. 
    // But only when I explicitly create a move-constructor in the form which was 
    // stated by Yakk, it is really that case. 
} 

void main() 
{ 
    vector<MyStruct> vecStructs; 
    MyFunc(vecStructs); 
} 
+2

'main'が' int'を返すべきことを除いて、あなたの例は問題ありません。 move構造は 'NewStruct'の* state *を' vecStructs'の新しい要素に移動するだけです。新しい要素は 'NewStruct'とは区別され、どちらの生涯も他の生涯と決してつながっていません。 'push_back'の代わりに[' std :: vector :: emplace_back'](http://en.cppreference.com/w/cpp/container/vector/emplace_back)の使用を検討してください。 –

+1

移動セマンティクスは「スタック上のスロット」を「削除」しません。「指定されていないが使用可能な状態」であるにもかかわらず、(あなたの例では 'NewStruct'から)移動されたオブジェクトが存在することが保証されています。 –

+1

'std :: move'は何も動かず、単なるキャストです:http://stackoverflow.com/questions/21358432/why-is-stdmove-named-stdmove – doctorlove

答えて

9

まず、std::moveが動かない、とstd::forwardは転送しません。

std::moveは、正の値の参照へのキャストです。通常、参照値の参照は、呼び出し側が実際にはデータを必要としないと約束しているため、データを移動することが許可されている参照として扱われます。

rvalue参照は暗黙的に戻り値std::move(時には転送)、一時オブジェクト、関数からローカルを返すとき、およびaのメンバを使用するときに暗黙的にバインドします。一時的または移動元のオブジェクト。

rvalue参照を取る関数内で何が起こるかは、魔法ではありません。問題のオブジェクト内でストレージを直接に要求することはできません。しかし、それはその腸を裂くことができます。そのような操作をより速く行うことができれば、引数に内部状態を混乱させる許可(慣例による)があります。

ここで、C++は自動的にいくつかの移動コンストラクタを作成します。この場合

struct MyStruct 
{ 
    int iInteger; 
    string strString; 
}; 

、それは大体このようなもの書きます:

MyStruct::MyStruct(MyStruct&& other) noexcept(true) : 
    iInteger(std::move(other.iInteger)), 
    strString(std::move(other.strString)) 
{} 

すなわち、それは要素毎の移動構造を行います。

整数を移動すると、何も起こりません。ソース整数の状態を乱しても何のメリットもありません。

std::stringを移動すると、効率が良くなります。 C++標準は、std::stringから別のものに移動するときに何が起こるかを記述します。基本的に、ソースstd::stringがヒープを使用している場合、ヒープストレージは宛先std::stringに転送されます。

これはC++コンテナの一般的なパターンです。それらから移動すると、ソースコンテナの「ヒープ割り当て」ストレージを盗み、それを宛先で再利用します。

ソースstd::stringは、std::stringのままです。ちょうど "裂け尽くされた"と言います。 std::stringがその保証をしているのであれば(SBOによるものではないかもしれません)、それは今は重要ではありません。

つまり、何かから移動すると、そのメモリは「再利用」されませんが、メモリが所有するは再利用できます。

MyStructにはヒープ割り当てメモリを使用できるstd::stringがあります。このヒープ割り当てメモリは、std::vectorに格納されているMyStructに移動できます。

"Hello"は、SBO(小さなバッファの最適化)が発生するほど短く、std::stringはヒープをまったく使用しません。この特定のケースでは、move ingのためにパフォーマンスが改善されない場合があります。

+0

@FrançoisAndrieuxどちらが理にかなっています。 SBOを使用すると、怠け者になり、コピーする代わりに移動するときに余分な作業をしなくて済むようになります。私は標準的な掘り出し物で確認するつもりはありません。問題ではないからです。 :) – Yakk

4

あなたの例では、に減少させることができる:これはまだ問題を提起

vector<string> vec; 
string str; // populate with a really long string 
vec.push_back(std::move(str)); 

、「それはローカルスタック変数を移動させることができます。」余分なコードを削除するだけで、理解しやすくなります。

答えははいです。std::string - 少なくともコンテンツが十分な大きさであれば、変数がスタック上にあっても実際のデータをヒープに格納するので、上記のようなコードはstd::moveから恩恵を受けることができます。

std::move()を使用しない場合、strというコンテンツを上記のようなコードでコピーすることができます。これは任意に大きくすることができます。 std::move()を使用すると、文字列の直接のメンバーだけがコピーされます(移動は古い場所をゼロにする必要はありません)。データは変更またはコピーせずに使用されます。

それは基本的にこの違いです:どちらの場合も

char* str; // populate with a really long string 
char* other = str; 

char* str; // populate with a really long string 
char* other = new char[strlen(str)+1]; 
strcpy(other, str); 

、変数はスタック上にあります。しかし、データはそうではありません。

std::string「小文字の文字列の最適化」が有効な場合、または整数を含む構造体の場合は、std::move()は何も購入しません。

関連する問題