C ++での移動セマンティクス-ローカル変数のMove-Return


10

私の理解では、C ++ 11では、関数からローカル変数を値で返す場合、コンパイラーはその変数をr値参照として扱い、それを関数から「移動」して返すことができます(もちろん、RVO / NRVOは代わりに発生しません)。

私の質問は、これは既存のコードを壊すことができないのですか?

次のコードを検討してください。

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

ローカルオブジェクトのデストラクタが暗黙的に移動されたオブジェクトを参照する可能性があるため、予期せず「空の」オブジェクトが表示される可能性があると考えていました。私はこれをテストしようとしましたが(http://ideone.com/ZURoeTを参照)、で明示的std::moveにせずに「正しい」結果を得ましたfoobar()。これはNRVOが原因だったと思いますが、コードを再配置して無効にすることはしませんでした。

この変換(関数からの移動を引き起こす)は暗黙的に発生し、既存のコードを壊す可能性があるという点で私は正しいのですか?

更新 これは私が話していることを説明する例です。次の2つのリンクは同じコード用です。 http://ideone.com/4GFIRu-C ++ 03 http://ideone.com/FcL2Xj-C ++ 11

出力を見ると異なります。

それで、この質問は今になると思いますが、これは標準に暗黙の移動を追加するときに考慮されましたか?この種類のコードは十分にまれなので、この重大な変更を追加しても問題ないと判断されましたか?また、このような場合にコンパイラが警告するかどうかも疑問に思います...


移動では、オブジェクトを常に破壊可能な状態にする必要があります。
Zan Lynx

ええ、でもそれは問題ではありません。c ++ 11より前のコードでは、ローカル変数の値が単にそれを返すために変更されないことを想定しているため、この暗黙の移動によってその想定が崩れる可能性があります。
Bwmat 2014年

それが私の例で解明しようとしたものです。デストラクタを使用して、関数のローカル変数(のサブセット)の状態を、returnステートメントの実行後、ただし関数が実際に戻る前に検査できます。
Bwmat 2014年

これは、あなたが追加した例のすばらしい質問です。これを解明することができるプロからより多くの回答を得られることを願っています。私が与えることができる唯一の真のフィードバックは、次のとおりです。これが、オブジェクトが通常、データに対する非所有ビューを持たないようにする理由です。オブジェクトに非所有ビュー(生のポインターまたは参照)を与えるとセグメンテーション違反になる無害に見えるコードを書く方法は実際にはたくさんあります。必要に応じて、これについて適切な回答で詳しく説明することができますが、それはあなたが本当に知りたいことではないと思います。そしてところで、11は、たとえば新しいキーワードを定義するだけで、既存のコードを壊すことができることはすでに知られています。
Nir Friedman、

ええ、C ++ 11が古いコードを壊さないと主張していないことは知っていますが、これはかなり微妙であり、見逃すのは本当に簡単です(コンパイラエラー、警告、segfaultなし)
Bwmat

回答:


8

Scott Meyers がcomp.lang.c ++に投稿(2010年8月)して、移動コンストラクタの暗黙的な生成がC ++ 03クラスの不変式を壊す可能性がある問題について:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

ここでの問題は、C ++ 03では、Xそのvメンバーが常に5つの要素を持つという不変式があったことです。X::~X()はその不変式に頼っていましたが、新しく導入されたmoveコンストラクターはから移動しv、その長さをゼロに設定しました。

壊れた不変条件はXのデストラクタでのみ検出されるため、これは例に関連しています(ローカルオブジェクトのデストラクタが暗黙的に移動されるオブジェクトを参照する可能性があるため、予期せず空のオブジェクトが表示されます)。

C ++ 11は、既存のコードのいくつかを壊すことと、移動コンストラクターに基づいて有用な最適化を提供することの間のバランスを達成しようとします。

委員会は最初に、移動コンストラクターと移動割り当て演算子は、ユーザーによって提供されない場合はコンパイラーによって生成されるべきであると決定しました。

次に、これが本当にアラームの原因であると判断し、既存のコード(たとえば、明示的に定義されたデストラクタ)が壊れる可能性は非常に低くなるような方法で、移動コンストラクタと移動割り当て演算子の自動生成を制限しました。

ユーザー定義のデストラクタが存在する場合に暗黙のmoveコンストラクタの生成を防ぐことで十分だと考えるのは魅力的ですが、それは真実ではありません(詳細は、N3153-Implicit Move Must Goを参照)。

N3174 -移動しないように移動したりするには Stroupstrupは言います:

これは、単純な下位互換性の問題ではなく、言語設計の問題と考えています。古いコードを壊すことは簡単に回避できます(たとえば、C ++ 0xから移動操作を削除するだけです)が、C ++ 0xをより優れた言語にするには、移動操作を普及させることをC +を壊す価値のある主要な目標にすることです+98コード。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.