関数からunique_ptrを返す


367

unique_ptr<T>コピーの構築を許可せず、代わりに移動のセマンティクスをサポートします。それでも、unique_ptr<T>関数からを返し、戻り値を変数に割り当てることができます。

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

上記のコードはコンパイルされ、意図したとおりに機能します。では、この行1がコピーコンストラクタを呼び出さず、コンパイラエラーが発生するのはなぜですか。2代わりにlineを使用しなければならなかった場合、それは理にかなっています(lineを使用し2ても機能しますが、そうする必要はありません)。

私はC ++ 0xがこの例外を許可していることを知っています。unique_ptr戻り値は関数が終了するとすぐに破棄され、返されるポインターの一意性を保証する一時的なオブジェクトだからです。これがどのように実装されているのか知りたいのですが、コンパイラーで特別なケースになっているのでしょうか、それとも、これが利用する言語仕様に他の節があるのでしょうか?


仮に、ファクトリメソッドを実装している場合、ファクトリの出力を返すのに1または2を使用しますか?これは、1の最も一般的な使用法であると思います。適切なファクトリを使用すると、実際に構築されたものの所有権を呼び出し元に渡したいからです。
Xharlie

7
@Xharlie?どちらもの所有権を渡しunique_ptrます。問題全体は、1と2が同じことを達成するための2つの異なる方法であることです。
Praetorian

この場合、RVOはc ++ 0xでも行われます。unique_ptrオブジェクトの破棄は、main関数の終了後に実行されますが、foo終了時には実行されません。
ampawd

回答:


218

これが利用する言語仕様に他のいくつかの節がありますか?

はい、12.8§34および§35を参照してください。

特定の基準が満たされると、実装はクラスオブジェクトのコピー/移動構成を省略できます[...]このコピー/移動操作の省略はコピー省略と呼ばれ、[...]のreturnステートメントで許可されますクラスの戻り値型の関数、式が関数の戻り値型と同じcv非修飾型の不揮発性自動オブジェクトの名前である場合 [...]

コピー操作の除外の基準が満たされ、コピーするオブジェクトが左辺値で指定されている場合、オブジェクトが右辺値で指定されているかのように、コピーのコンストラクターを選択するオーバーロード解決が最初に実行されます。


最悪の場合、つまりC ++ 11、C ++ 14、およびC ++ 17で省略なしで、returnステートメントの名前付き値が処理されるため、値による戻りがデフォルトの選択である必要があるという点をもう1つ追加したかっただけです。右辺値として。たとえば、次の関数は-fno-elide-constructorsフラグを指定してコンパイルします

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

コンパイル時にフラグを設定すると、この関数で2つの移動(1と2)が発生し、その後(3)で1つの移動が発生します。


@juanchopanza基本的foo()に、関数内の戻り値と同様に、それが実際に破棄されようとしている(何かに割り当てられていない場合)ので、C ++がmoveコンストラクターを使用することは理にかなっていunique_ptr<int> p = foo();ますか?
7cows 2013年

1
この回答は、実装何かを行うことを許可されていることを示しています...する必要はありません。したがって、これが唯一の関連セクションである場合、この動作に依存することは移植不可能です。しかし、私はそうではないと思います。Nikola SmiljanicとBartosz Milewskiの回答で説明されているように、正しい回答はmoveコンストラクターと関係があると思う傾向があります。
Don Hatch

6
@DonHatchそれらの場合にコピー/移動省略を実行することは「許可」されていると述べていますが、ここではコピー省略について話していません。これは、ここで適用される2番目の引用段落であり、コピー省略ルールに便乗していますが、コピー省略自体ではありません。2番目の段落には不確実性はありません。完全に移植可能です。
ジョセフマンスフィールド

@juanchopanzaこれは2年後のことだと思いますが、それでも間違っていると思いますか 前のコメントで述べたように、これはコピーの省略に関するものではありません。たまたま、コピー省略が適用される場合(たとえで適用できない場合でもstd::unique_ptr)、最初にオブジェクトを右辺値として扱う特別なルールがあります。これはニコラの答えと完全に一致すると思います。
ジョセフマンスフィールド

1
それで、この例とまったく同じように返すときに、移動専用タイプ(削除されたコピーコンストラクター)に対して「削除された関数を参照しようとしています」というエラーが発生するのはなぜですか?
DrumM

104

これは決して特定のものではありません std::unique_ptrで移動可能なすべてのクラスに適用されます。値で返すため、言語ルールによって保証されます。コンパイラはコピーを省略しようとし、コピーを削除できない場合は移動コンストラクターを呼び出し、移動できない場合はコピーコンストラクターを呼び出し、コピーできない場合はコンパイルに失敗します。

std::unique_ptr引数として受け入れる関数がある場合、それにpを渡すことはできません。明示的にmoveコンストラクターを呼び出す必要がありますが、この場合、への呼び出しの後に変数pを使用しないでくださいbar()

void bar(std::unique_ptr<int> p)
{
    // ...
}

int main()
{
    unique_ptr<int> p = foo();
    bar(p); // error, can't implicitly invoke move constructor on lvalue
    bar(std::move(p)); // OK but don't use p afterwards
    return 0;
}

3
@フレッド-まあ、そうではありません。けれどもpその結果、一時的ではありませんfoo()返されているもの、です。したがって、それは右辺値であり、移動することができ、割り当てをmain可能にします。ニコラがこのルールを誤ってp自分自身に適用しているように見えることを除いて、あなたは間違っていたと思います。
Edward Strange

正確に言いたかったのですが、言葉が見つかりませんでした。あまり明確ではなかったので、その部分を削除しました。
NikolaSmiljanić2010年

質問があります。元の質問では、Line 1とLineの間に大きな違いはあります2か?私の見解でpmain、で構築するときと同じですが、それはの戻り値の型のタイプのみに関心がありfooますよね?
Hongxu Chen 2014

1
@HongxuChenその例ではまったく違いはありません。受け入れられた回答の標準からの引用を参照してください。
NikolaSmiljanić2014

実際には、割り当てを行う限り、後でpを使用できます。それまでは、コンテンツを参照することはできません。
アラン

38

unique_ptrには、従来のコピーコンストラクターがありません。代わりに、右辺値参照を使用する「移動コンストラクタ」があります。

unique_ptr::unique_ptr(unique_ptr && src);

右辺値参照(2つのアンパサンド)は右辺値にのみバインドされます。そのため、左辺値のunique_ptrを関数に渡そうとすると、エラーが発生します。一方、関数から返された値は右辺値として扱われるため、移動コンストラクターは自動的に呼び出されます。

ちなみに、これは正しく動作します:

bar(unique_ptr<int>(new int(44));

ここでの一時的なunique_ptrは右辺値です。


8
ポイントはもっと重要だと思います。なぜp「明らかに」左辺値がの定義のreturnステートメントで右辺値として扱われるのでしょうか。関数自体の戻り値が「移動」できるということに問題はないと思います。return p;foo
CBベイリー

関数からの戻り値をstd :: moveでラップすると、2回移動されることになりますか?

3
@RodrigoSalazar std :: moveは、左辺値参照(&)から右辺値参照(&&)への豪華なキャストです。右辺値参照でのstd :: moveの余分な使用は、単に何もしない
TiMoch

13

Scott MeyersのEffective Modern C ++のアイテム25で完全に説明されていると思います。ここに抜粋があります:

RVOを祝福する標準の一部は、RVOの条件は満たされているが、コンパイラーがコピー省略を実行しないことを選択した場合、返されるオブジェクトは右辺値として扱われる必要があると述べています。実際、標準では、RVOが許可されている場合、コピー省略が行われるかstd::move、返されるローカルオブジェクトに暗黙的に適用されることが要求されています。

ここでは、RVOは指し値の最適化を返すと、RVOのための条件が満たされている場合は、ローカルオブジェクトを返す手段は、あなたが行うことを期待することを関数内で宣言RVOもきれいに参照することにより、彼の本の項目25で説明され、標準(ここでは、ローカルオブジェクトには、returnステートメントによって作成された一時オブジェクトが含まれます)。抜粋の最大の利点std::move、コピーの省略が行われるか、返されるローカルオブジェクトに暗黙的に適用されることです。スコットは項目25で、std::move、コンパイラーがコピーを省略しないことを選択したときに暗黙的に適用され、プログラマーが明示的に削除してはならないこと。

あなたのケースでは、コードはローカルオブジェクトを返し、の型は戻り型と同じであるため、RVOの候補として明らかにpなり、pコピーが省略されます。そして、もしコンパイラーが何らかの理由でコピーを省略しないことを選択した場合、std::moveラインに追いやられたでしょう1


5

私が他の答えで見なかった一つのことは関数内で作成されたstd :: unique_ptrを返すことと、その関数に与えられたstd :: unique_ptrを返すことには違いがあるという別の答えを明確にするため。

例は次のようになります。

class Test
{int i;};
std::unique_ptr<Test> foo1()
{
    std::unique_ptr<Test> res(new Test);
    return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
    // return t;  // this will produce an error!
    return std::move(t);
}

//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));

それは回答でfredoverflowによって言及されています -明らかにハイライトされた「自動オブジェクト」参照(右辺値参照を含む)は自動オブジェクトではありません。
Toby Speight 2017

@TobySpeightわかりました。私のコードはその時の説明に過ぎないと思います。
v010dya 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.