434

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

#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がコピーコンストラクターを呼び出さず、コンパイラーエラーが発生するのはどうしてですか?代わりにlineを使用する必要がある場合は、2それは理にかなっています( lineを使用する2こともできますが、必須ではありません)。

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

4

6 に答える 6

254

これが悪用する言語仕様に他の条項はありますか?

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

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

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


最悪の場合、つまり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つの移動が発生します。

于 2010-11-30T18:06:56.860 に答える
120

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

引数として受け入れる関数がある場合、std::unique_ptrpを渡すことはできません。明示的に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;
}
于 2010-11-30T17:48:39.980 に答える
42

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

unique_ptr::unique_ptr(unique_ptr && src);

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

ちなみに、これは正しく機能します:

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

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

于 2010-11-30T23:00:27.760 に答える
19

それはScottMeyersのEffectiveModernC++の項目25で完全に説明されていると思います。抜粋は次のとおりです。

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

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

あなたの場合、コードは明らかにRVOの候補です。これは、ローカルオブジェクトpを返し、のタイプがp戻りタイプと同じであるため、コピーの省略が発生するためです。そして、コンパイラが何らかの理由でコピーを削除しないことを選択した場合、std::move行にキックインします1

于 2017-10-04T18:38:45.720 に答える
9

私が他の答えで見なかったことの1つは関数内で作成された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));
于 2017-07-03T16:06:44.533 に答える
6

std :: move()を使用しなければならない1つのケースについて言及したいと思います。そうしないと、エラーが発生します。ケース:関数の戻り型がローカル変数の型と異なる場合。

class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
     std::unique_ptr<Derived> derived(new Derived());
     return std::move(derived); //std::move() must
}

参照:https ://www.chromium.org/developers/smart-pointer-guidelines

于 2020-06-22T12:42:09.240 に答える