1. ホーム
  2. c++

[解決済み] コンストラクタや関数に unique_ptr 引数を渡すにはどうしたらいいですか?

2022-03-22 02:37:25

質問

C++11のmoveセマンティクスに慣れていないため、以下の処理方法がよくわかりません。 unique_ptr パラメータを使用します。このクラスが自分自身を参照することを考えてみましょう。

#include <memory>

class Base
{
  public:

    typedef unique_ptr<Base> UPtr;

    Base(){}
    Base(Base::UPtr n):next(std::move(n)){}

    virtual ~Base(){}

    void setNext(Base::UPtr n)
    {
      next = std::move(n);
    }

  protected :

    Base::UPtr next;

};

を取る関数はこのように書けばいいのでしょうか? unique_ptr の引数を取ることができますか?

また std::move を呼び出すコードで使用できますか?

Base::UPtr b1;
Base::UPtr b2(new Base());

b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?

解決方法は?

一意なポインターを引数として受け取るために可能な方法と、それに関連する意味を説明します。

(A) 値によって

Base(std::unique_ptr<Base> n)
  : next(std::move(n)) {}

ユーザーがこれを呼び出すには、次のいずれかを行う必要があります。

Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));

ユニークポインタを値で取るということは 転送 ポインタの所有権は、問題の関数/オブジェクト/その他にあります。後 newBase が構築されます。 nextBase であることが保証されています。 . あなたはそのオブジェクトを所有していませんし、そのポインタさえももう持っていません。それはもうありません。

これは、パラメータを値で受け取るので確実です。 std::move は実際には 移動 のようなもので、単なる派手なキャストです。 std::move(nextBase) を返します。 Base&& へのr値参照である。 nextBase . それだけなんです。

なぜなら Base::Base(std::unique_ptr<Base> n) は引数を r-value reference ではなく value で取るので、C++ は自動的にテンポラリを構築してくれます。これは std::unique_ptr<Base> から Base&& を介して関数に与えた std::move(nextBase) . この仮設を作ることで、実際に 動く の値を nextBase を関数の引数 n .

(B) 非恒等式l値参照によるもの

Base(std::unique_ptr<Base> &n)
  : next(std::move(n)) {}

これは実際のl値(名前付き変数)に対して呼び出されなければなりません。このように一時的なものでは呼び出すことができません。

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

この意味は、他の非恒等式参照の使い方と同じである。 または ポインタの所有権を主張します。このコードを考えると

Base newBase(nextBase);

という保証はありません。 nextBase は空です。それは かもしれない 空っぽかもしれないし、空っぽでないかもしれない。それは本当に Base::Base(std::unique_ptr<Base> &n) がしたいのです。そのため、関数のシグネチャを見ただけでは、何が起こるのかがよくわかりません。

そのため、インターフェースとしてはあまりお勧めできません。

(C) const l値参照による

Base(std::unique_ptr<Base> const &n);

実装を示さないのは、あなたが できない からの移動 const& . を渡すことで const& にアクセスできることを意味しています。 Base はポインタを経由していますが ストア をどこかに置く。所有権を主張することもできません。

これは便利です。あなたの特定のケースに当てはまるとは限りませんが、誰かにポインタを渡して、そのポインタが できない (のようなC++のルールを破ることなく)。 const ) 所有権を主張する。保存することはできません。他の人に渡すことはできますが、他の人も同じ規則に従わなければなりません。

(D) r値参照による

Base(std::unique_ptr<Base> &&n)
  : next(std::move(n)) {}

これは、"by non-const l-value reference"の場合とほぼ同じです。違うのは2点です。

  1. あなたは できる テンポラリーを渡す。

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
    
    
  2. あなた 必須 使用 std::move は、一時的でない引数を渡すときに使用します。

本当に問題なのは後者です。もし、この行を見たら

Base newBase(std::move(nextBase));

この行が完了した後、あなたは合理的な期待を持っています。 nextBase は空であるべきです。から移動しているはずです。結局のところ、あなたはその std::move そこに座って、移動が起こったことを伝えているのです。

問題は、そうなっていないことです。それは 保証 から移動していること。それは かもしれません から移動していますが、ソースコードを見なければわかりません。関数のシグネチャだけではわかりません。

推薦の言葉

  • (A)価値観で。 もし、ある機能が主張することを意味するのであれば 所有権 unique_ptr の場合、値で受け取る。
  • (C) const l値参照によって。 もし、ある関数が単に unique_ptr は、その関数が実行されている間、それを const& . または & または const& を使用するのではなく、実際に指された型に対して unique_ptr .
  • (D)r値参照で。 関数が所有権を主張したりしなかったりする場合(内部コードのパスに依存する)、その関数を && . しかし、私は可能な限りこれをしないことを強くお勧めします。

unique_ptrの操作方法

をコピーすることはできません。 unique_ptr . 移動のみ可能です。これを行うには、適切な方法は std::move 標準ライブラリ関数

を取ると unique_ptr を値で指定すると、そこから自由に移動することができます。しかし、移動が実際に行われないのは std::move . 次のような文章を考えてみましょう。

std::unique_ptr<Base> newPtr(std::move(oldPtr));

これは実際には2つのステートメントです。

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(注:一時的でないr値の参照は実際のr値ではないので、上記のコードは技術的にコンパイルできません。これはデモのためだけにあります)。

temporary へのr値参照に過ぎない。 oldPtr . それは コンストラクタ newPtr 移動が発生する場所。 unique_ptr のmoveコンストラクタ(このコンストラクタには && を自分自身に置き換えることで、実際の動きを行っています。

もし、あなたが unique_ptr の値で、それをどこかに保存したい場合は 必ず 使用 std::move を使用して保存します。