9

この機能がどのように機能するのか正確に知りたいです。次のようなものを考えてください

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

このコードは、コンパイラーが暗黙的に移動するため、移動専用タイプでも正常にコンパイルされます。しかし、論理的には、戻り式の場合、結果がローカル変数を参照しているかどうかを判断することで停止性問題が解決されます。コンパイラがすべてのローカル変数を戻り式の右辺値として単純に処理した場合、これは変数として問題になります。その1つの式で複数回参照される場合があります。ローカルに直接参照が1つしかない場合でも、他の間接エイリアスがないことを証明することはできません。

では、コンパイラは、戻り式からいつ移動するかをどのように知るのでしょうか。

4

4 に答える 4

10

簡単なルールがあります。コピーの省略の条件が満たされている場合(変数が関数パラメーターである場合を除く)、右辺値として扱います。それが失敗した場合は、左辺値として扱います。それ以外の場合は、左辺値として扱います。

§12.8 [class.copy] p32

ソースオブジェクトが関数パラメーターであり、コピーされるオブジェクトが左辺値で指定されていることを除いて、コピー操作の省略基準が満たされている、または満たされる場合、コピーのコンストラクターを選択するためのオーバーロード解決は次のようになります。最初は、オブジェクトが右辺値で指定されているかのように実行されます。オーバーロード解決が失敗した場合、または選択したコンストラクターの最初のパラメーターのタイプがオブジェクトのタイプへの右辺値参照ではない場合(おそらくcv修飾)、オブジェクトを左辺値と見なして、オーバーロード解決が再度実行されます。[注:この2段階の過負荷解決は、コピーの省略が発生するかどうかに関係なく実行する必要があります。省略が実行されない場合に呼び出されるコンストラクターを決定し、呼び出しが省略された場合でも、選択されたコンストラクターにアクセスできる必要があります。—エンドノート]

例:

template<class T>
T f(T v, bool b){
  T t;
  if(b)
    return t; // automatic move
  return v; // automatic move, even though it's a parameter
}

次のコードには自動移動がないため、私はそのルールに個人的に同意するわけではありません。

template<class T>
struct X{
  T v;
};

template<class T>
T f(){
  X<T> x;
  return x.v; // no automatic move, needs 'std::move'
}

私のこの質問も参照してください。

于 2012-07-15T15:06:47.693 に答える
5

標準では「ローカル変数を参照する」とは言われていませんが、「不揮発性の自動オブジェクトの名前です」と書かれています。

§12.8 [class.copy] p31

[...]コピーの省略と呼ばれるこのコピー/移動操作の省略は、次の状況で許可されます[...]:

  • returnクラス戻り型を持つ関数のステートメントで、式が不揮発性自動オブジェクトの名前である場合[...]

したがって、ポインタまたは参照は許可されていません。意図された可能性が低い解釈を悪用しようとする邪悪な方法でそれを読む場合、それは以下を動かすことを意味すると言うことができます

struct A {
  std::unique_ptr<int> x;
  std::unique_ptr<int> f() { return x; }
};

int main() { A a; a.f(); }

この場合、戻り式は自動保存期間を持つ変数の名前です。規格の他のいくつかの段落は複数の方法で解釈することができますが、規則は意図されている可能性が最も高い解釈を採用することです。

于 2012-07-15T15:10:16.817 に答える
3

しかし、論理的には、戻り式の場合、結果がローカル変数を参照しているかどうかを判断することで、停止性問題を解決できます。

あなたは問題の複雑さを過大評価しています。明らかに、コンパイラは、戻り式がローカルの変数の1つに言及しているかどうかを確認し、とにかくデストラクタを呼び出すためにそれらすべての変数を追跡する必要があります。戻り値が変数に明示的に言及している場合にのみ移動することに注意してください。ローカル変数へのポインターまたは参照を返す場合は、そうする必要はありません。

std::unique_ptr<int>& same( std::unique_ptr<int>& x ) { return x; }
std::unique_ptr<int> foo() {
   std::unique_ptr<int> p( new int );
   std::unique_ptr<int>& r = same(p);
   return r;                           // FAIL
}
于 2012-07-15T16:58:43.073 に答える
2

ここでコンパイラの容量を過大評価していると思います。

ローカル変数を直接返す場合、作業は簡単です。移動できます。

ローカル変数からの移動が必要な式を呼び出す場合は、手動で指定する必要があります。

ここでいくつかの例を参照してください。

于 2012-07-15T15:11:00.120 に答える