459

私はC++11でセマンティクスを移動するのは初めてですが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?
4

7 に答える 7

937

一意のポインタを引数として使用するための可能な方法と、それに関連する意味を次に示します。

(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ません。それはただの派手なキャストです。へのr値参照であるaを返します。それがすべてです。std::move(nextBase)Base&&nextBase

Base::Base(std::unique_ptr<Base> n)引数はr値参照ではなく値で取得されるため、C++は自動的に一時的なものを作成します。を介して関数を指定したstd::unique_ptr<Base>からを作成します。値をから関数の引数に実際に移動するのは、この一時的な構造です。Base&&std::move(nextBase)nextBasen

(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)constl-value参照による

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

から移動できないため、実装は示していませんconst&。を渡すことconst&で、関数はポインタを介してviaにアクセスできますが、どこにも格納Baseできないことを意味します。所有権を主張することはできません。

これは便利です。必ずしも特定のケースに当てはまるわけではありませんが、誰かにポインタを渡して、その所有権を主張できないことを知っておくとよいでしょconst。彼らはそれを保存することはできません。彼らはそれを他の人に渡すことができますが、それらの他の人は同じ規則に従わなければなりません。

(D)r値参照による

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

これは、「非定数l値参照による」場合とほぼ同じです。違いは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-value参照による: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です。これは、移動が発生する場所のコンストラクターにあります。の移動コンストラクター(それ自体を取得するコンストラクター)は、実際の移動を実行するものです。newPtrunique_ptr&&

値がunique_ptrあり、それをどこかに保存したい場合は、を使用して保存する必要std::moveがあります。

于 2011-11-13T21:32:58.480 に答える
67

std::unique_ptrクラス テンプレートのインスタンスによってメモリが管理されるオブジェクトにポインターを渡すさまざまな実行可能なモードを述べてみましょう。それは古いクラス テンプレートにも適用されますstd::auto_ptr(一意のポインターが行うすべての使用を許可すると思いますが、さらに、を呼び出す必要なく、右辺値が期待される場所で変更可能な左辺値が受け入れられますstd::move) std::shared_ptr

議論の具体例として、次の単純なリストタイプを検討します

struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }

このようなリストのインスタンス (他のインスタンスと部分を共有したり、循環したりすることはできません) は、最初のlistポインターを保持する人によって完全に所有されます。格納するリストが決して空にならないことをクライアント コードが認識している場合、クライアント コードnodeは、list. のデストラクタをnode定義する必要はありません。そのフィールドのデストラクタは自動的に呼び出されるため、初期ポインタまたはノードの有効期間が終了すると、リスト全体がスマート ポインタ デストラクタによって再帰的に削除されます。

この再帰型は、プレーン データへのスマート ポインターの場合にはあまり目立たないいくつかのケースについて説明する機会を与えてくれます。また、関数自体が (再帰的に) クライアント コードの例を提供することもあります。の typedeflistはもちろん に偏っていますが、定義は useまたはにunique_ptr変更することができますが、以下に述べる内容をあまり変更する必要はありません (特に、デストラクタを記述する必要なく保証される例外の安全性に関して)。auto_ptrshared_ptr

スマート ポインターを渡すモード

モード 0: スマート ポインターの代わりにポインターまたは参照引数を渡す

関数が所有権に関係ない場合は、これが推奨される方法です。スマート ポインターをまったく使用しないでください。この場合、関数は、指しているオブジェクトの所有者や、所有権がどのような方法で管理されているかを気する必要がないため、生のポインターを渡すことは完全に安全であり、最も柔軟な形式です。所有権に関係なく、クライアントは常に生のポインターを生成します (メソッドを呼び出すかget、アドレス演算子から&)。

たとえば、そのようなリストの長さを計算する関数には、list引数ではなく、生のポインターを与える必要があります。

size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }

変数を保持するクライアントは、list headこの関数を として呼び出すことができますが、代わりに空でないリストを表すlength(head.get())を格納することを選択したクライアントは、 を呼び出すことができます。node nlength(&n)

ポインターが非 null であることが保証されている場合 (リストが空である可能性があるため、ここではそうではありません)、ポインターではなく参照を渡すことを好むかもしれません。const関数がノードのコンテンツを追加または削除せずにノードのコンテンツを更新する必要がある場合は、 non- へのポインター/参照である可能性があります (後者には所有権が含まれます)。

モード 0 のカテゴリに該当する興味深いケースは、リストの (深い) コピーを作成することです。これを行う関数はもちろん、作成したコピーの所有権を譲渡する必要がありますが、コピーしているリストの所有権には関係ありません。したがって、次のように定義できます。

list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }

このコードは、なぜそれがコンパイルされるのかという疑問のために、よく見る価値があります (イニシャライザ リスト内の への再帰呼び出しの結果は、のフィールドを初期化するときに、別名copyのムーブ コンストラクタ内の右辺値参照引数にバインドされます)。 generated )、およびなぜそれが例外セーフであるかについての質問に対して (再帰的な割り当てプロセス中にメモリが不足し、throwsの呼び出しが発生した場合、その時点で、部分的に構築されたリストへのポインターが一時的な型に匿名で保持されます初期化子リスト用に作成され、そのデストラクタはその部分的なリストをクリーンアップします)。ちなみに、(私が最初に行ったように)2番目を次のように置き換えたいという誘惑に抵抗する必要があります。unique_ptr<node>listnextnodenewstd::bad_alloclistnullptrp、これは結局、その時点で null であることがわかっています。null であることがわかっている場合でも、(生の) ポインターから constant へのスマート ポインターを構築することはできません。

モード 1: スマート ポインターを値で渡す

スマート ポインター値を引数として受け取る関数は、指しているオブジェクトをすぐに取得します。呼び出し元が保持していたスマート ポインター (名前付き変数または無名一時変数のいずれか) は、関数の入り口で引数値にコピーされ、呼び出し元のポインターが null になりました (一時的な場合、コピーは省略された可能性がありますが、いずれにせよ、呼び出し元は指定されたオブジェクトへのアクセスを失いました)。このモード呼び出しを現金で呼び出したいと思います。呼び出し元は、呼び出したサービスに対して前払いし、呼び出し後に所有権について幻想を持つことはできません。これを明確にするために、言語規則では、呼び出し元が引数をラップする必要があります。std::moveスマート ポインターが変数に保持されている場合 (技術的には、引数が左辺値の場合)。この場合 (ただし、以下のモード 3 の場合は除きます)、この関数はその名前が示すように、値を変数から一時変数に移動し、変数を null のままにします。

呼び出された関数がポイント先のオブジェクトの所有権を無条件に取得す​​る (盗む) 場合、このモードを使用するstd::unique_ptrstd::auto_ptr、所有権と共にポインターを渡すのに適した方法です。これにより、メモリ リークのリスクが回避されます。それにもかかわらず、以下のモード 3 がモード 1 よりも優先されない (ほんの少しだけ) 状況はごくわずかだと思います。このため、このモードの使用例は提供しません。(ただし、reversed以下のモード 3 の例を参照してください。ここでは、モード 1 も少なくとも同様に機能することが示されています。) 関数がこのポインターだけでなくより多くの引数を取る場合、モードを回避するための技術的な理由がさらにある可能性があります。 1 (std::unique_ptrまたはを使用std::auto_ptr): 実際の移動操作はポインタ変数の受け渡し中に行われるためp式によって、が他の引数を評価している間 (評価の順序が指定されていない) 有用な値を保持してstd::move(p)いると仮定することはできず、p微妙なエラーにつながる可能性があります。対照的に、モード 3 を使用すると、関数呼び出しの前に移動が行われないことが保証されるpため、他の引数は を介し​​て安全に値にアクセスできますp

で使用するとstd::shared_ptr、このモードは興味深い点で、単一の関数定義を使用して、関数で使用される新しい共有コピーを作成しながら、呼び出し元がポインターの共有コピーを保持するかどうかを選択できます(これは、左辺値が引数が提供される; 呼び出しで使用される共有ポインターのコピー コンストラクターが参照カウントを増やす)、またはポインターを保持したり参照カウントに触れたりせずに関数にポインターのコピーを与えるだけである (これは、右辺値引数が提供された場合に発生する可能性があります)。の呼び出しでラップされた左辺値std::move)。例えば

void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container

void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
  f(p); // lvalue argument; store pointer in container but keep a copy
  f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
  f(std::move(p)); // xvalue argument; p is transferred to container and left null
}

void f(const std::shared_ptr<X>& x)同じことは、(左辺値の場合) と(右辺値の場合) を別々に定義することで実現できます。void f(std::shared_ptr<X>&& x)関数本体は、最初のバージョンがコピー セマンティクスを呼び出す (を使用する場合はコピーの構築/代入を使用するx) という点だけが異なりますが、2 番目のバージョンはセマンティクスを移動します。 (std::move(x)コード例のように、代わりに書き込みます)。そのため、共有ポインターの場合、コードの重複を避けるためにモード 1 が役立ちます。

モード 2: (変更可能な) 左辺値参照によってスマート ポインターを渡す

ここで、関数はスマート ポインターへの変更可能な参照を持つことだけを必要としますが、それで何をするかについては何も示しません。このメソッドcall by cardを呼び出したいと思います。呼び出し元は、クレジット カード番号を指定して支払いを保証します。参照は、指定されたオブジェクトの所有権を取得するために使用できますが、そうする必要はありません。このモードでは、変更可能な左辺値引数を提供する必要があります。これは、関数の目的の効果には、引数変数に有用な値を残すことが含まれる可能性があるという事実に対応しています。そのような関数に渡したい右辺値式を持つ呼び出し元は、呼び出しを行うことができるように名前付き変数に格納する必要があります。これは、言語が定数への暗黙的な変換しか提供しないためです。右辺値からの左辺値参照 (一時参照)。( によって処理される反対の状況とは異なり、からへのスマート ポインター型でstd::moveのキャストは不可能です。それにもかかわらず、この変換は、本当に必要な場合は単純なテンプレート関数によって取得できます。https://stackoverflow.com/a/24868376を参照してください)。 /1436796 )。呼び出された関数が引数を盗んで無条件にオブジェクトの所有権を取得しようとする場合、左辺値引数を提供する義務は間違ったシグナルを与えています: 変数は呼び出し後に有用な値を持たなくなります。したがって、関数内で同じ可能性を提供しますが、呼び出し元に右辺値を提供するように要求するモード 3 は、そのような使用法に優先する必要があります。Y&&Y&Y

ただし、モード 2 には有効な使用例があります。つまり、ポインターを変更する可能性のある関数、または所有権を伴う方法で指されたオブジェクトです。たとえば、ノードを の前に付ける関数は、listそのような使用例を提供します。

void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }

ここで、呼び出し元に を強制的に使用させるのは明らかに望ましくありませんstd::move。呼び出し後もスマート ポインターは明確に定義された空でないリストを保持していますが、以前とは異なります。

prependここでも、空きメモリが不足して呼び出しが失敗した場合に何が起こるかを観察するのは興味深いことです。次に、new呼び出しがスローされstd::bad_allocます。この時点では nonodeを割り当てることができなかったため、渡された rvalue 参照 (モード 3)std::move(l)はまだ盗まれていないことが確実です。これは、割り当てに失敗した のnextフィールドを構築するために行われるからです。nodeしたがって、エラーがスローされたとき、元のスマート ポインターlは元のリストを保持しています。そのリストは、スマート ポインター デストラクタによって適切に破棄されるかl、十分に早い節のおかげで生き残る必要がある場合はcatch、元のリストを保持します。

これは建設的な例でした。この質問にウィンクすると、指定された値を含む最初のノードがあれば、それを削除するというより破壊的な例を示すこともできます。

void remove_first(int x, list& l)
{ list* p = &l;
  while ((*p).get()!=nullptr and (*p)->entry!=x)
    p = &(*p)->next;
  if ((*p).get()!=nullptr)
    (*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next); 
}

ここでも、正確さは非常に微妙です。特に、最後のステートメントでは、削除されるノード内に保持されているポインターは、そのノードを (暗黙的に) 破棄する前に(*p)->next(によって保持されている古い値を破棄するときに)リンクが解除されます (によってrelease、ポインターは返されますが、元の nullになります)。その時点で 1つのノードのみが破棄されます。(コメントで言及されている別の形式では、このタイミングはインスタンスの move-assignment 演算子の実装の内部に委ねられます。標準では 20.7.1.2.3;2 とあり、この演算子は「あたかも"を呼び出しているため、ここでもタイミングは安全です。) resetpstd::unique_ptrlistreset(u.release())

prependandは、常に空でないリストremove_firstのローカル変数を格納するクライアントからは呼び出せないことに注意してください。これは、指定された実装がそのような場合に機能しないためです。node

モード 3: (変更可能な) 右辺値参照によってスマート ポインターを渡す

これは、単にポインターの所有権を取得する場合に使用する優先モードです。このメソッド呼び出しを小切手で呼び出したいと思います: 呼び出し元は、小切手に署名することによって、現金を提供するかのように所有権の放棄を受け入れる必要がありますが、実際の引き出しは、呼び出された関数が実際にポインターを盗むまで延期されます (モード 2 を使用する場合とまったく同じように) )。「チェックの署名」とは、具体的には、呼び出し元がstd::move左辺値の場合に (モード 1 のように) 引数をラップする必要があることを意味します (右辺値の場合、「所有権を放棄する」部分は明らかであり、別のコードは必要ありません)。

技術的には、モード 3 はモード 2 とまったく同じように動作するため、呼び出された関数は所有権を引き受ける必要がないことに注意してください。ただし、(通常の使用で) 所有権の譲渡に不確実性がある場合は、モード 3 よりもモード 2 を使用することをお勧めします。これにより、モード 3 を使用することで、発信者が所有権を放棄していることを暗示的に示すことができます。モード 1 の引数の受け渡しだけが、実際には呼び出し側に所有権の強制的な喪失を通知していると反論する人もいるかもしれません。しかし、クライアントが呼び出された関数の意図について疑問を持っている場合、クライアントは呼び出されている関数の仕様を知っているはずであり、それによって疑いが取り除かれます。

listモード 3 の引数渡しを使用する型を含む典型的な例を見つけるのは驚くほど困難です。bリストを別のリストの最後に移動することaは典型的な例です。ただし、a(操作の結果を保持して保持する) モード 2 を使用して渡す方が適切です。

void append (list& a, list&& b)
{ list* p=&a;
  while ((*p).get()!=nullptr) // find end of list a
    p=&(*p)->next;
  *p = std::move(b); // attach b; the variable b relinquishes ownership here
}

モード 3 引数の受け渡しの純粋な例は次のとおりです。これは、リスト (およびその所有権) を受け取り、逆の順序で同一のノードを含むリストを返します。

list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
  list result(nullptr);
  while (p.get()!=nullptr)
  { // permute: result --> p->next --> p --> (cycle to result)
    result.swap(p->next);
    result.swap(p);
  }
  return result;
}

この関数はl = reversed(std::move(l));、リストを反転してそれ自体にするために のように呼び出すことができますが、反転したリストは別の方法で使用することもできます。

ここでは、効率のために引数がすぐにローカル変数に移動されます (引数lを の代わりに直接使用することもできますpが、毎回それにアクセスすると、余分なレベルの間接化が必要になります)。したがって、モード 1 引数の受け渡しとの違いは最小限です。実際、そのモードを使用すると、引数はローカル変数として直接機能する可能性があるため、最初の移動を回避できます。これは、参照によって渡された引数がローカル変数を初期化するためだけに機能する場合、代わりに値によって渡し、パラメーターをローカル変数として使用するという一般原則の単なる例です。

モード 3 を使用することは、モード 3 を使用してスマート ポインターの所有権を転送するすべての提供されたライブラリ関数によって証明されるように、標準によって提唱されているようですstd::shared_ptr<T>(auto_ptr<T>&& p)。そのコンストラクターは (in でstd::tr1) 変更可能な左辺値参照 (コピー コンストラクターと同様) を取得するために使用されたため、 inのように左辺値auto_ptr<T>&で呼び出すことができ、その後null にリセットされました。引数の受け渡しがモード 2 から 3 に変更されたため、この古いコードを書き直す必要があり、その後も引き続き機能します。委員会がここでモード 2 を好まなかったことは理解していますが、モード 1 に変更するオプションがありました。auto_ptr<T>pstd::shared_ptr<T> q(p)pstd::shared_ptr<T> q(std::move(p))std::shared_ptr<T>(auto_ptr<T> p)代わりに、古いコードが変更なしで機能することを保証できたはずです。これは、(一意のポインターとは異なり) 自動ポインターは値に対して暗黙的に逆参照できるためです (ポインター オブジェクト自体はプロセスで null にリセットされます)。どうやら、委員会はモード 1 よりもモード 3 を提唱することを非常に好んだため、すでに非推奨の使用法であってもモード 1 を使用するのではなく、既存のコードを積極的に壊すことを選択したようです。

モード 1 よりもモード 3 を好む場合

多くの場合、モード 1 は完全に使用可能であり、上記の例のように、所有権がスマート ポインターをローカル変数に移動する形式をとる場合は、モード 3 よりも優先される可能性がありますreversed。ただし、より一般的なケースでモード 3 を好む理由が 2 つあります。

  • 一時ポインタを作成して古いポインタを nix するよりも、参照を渡す方がわずかに効率的です (キャッシュの処理はやや面倒です)。シナリオによっては、ポインターが実際に盗まれる前に、別の関数に変更されずに数回渡されることがあります。このような受け渡しには通常、書き込みが必要ですがstd::move(モード 2 が使用されない限り)、これは単なるキャストであり、実際には何も行わない (特に逆参照を行わない) ため、コストはゼロであることに注意してください。

  • 関数呼び出しの開始と、それ (または含まれている呼び出し) が実際にポイント先のオブジェクトを別のデータ構造に移動するポイントとの間に何かが例外をスローすることが考えられるはずです (この例外は、関数自体の内部でまだキャッチされていません)。 )、モード 1 を使用する場合、スマート ポインターによって参照されるオブジェクトは、catch節が例外を処理できるようになる前に破棄されます (スタックの巻き戻し中に関数パラメーターが破棄されたため) が、モード 3 を使用する場合はそうではありません。後者は、そのような場合、呼び出し元には、(例外をキャッチすることによって) オブジェクトのデータを回復するオプションがあります。ここでのモード 1はメモリ リークを引き起こしませんが、プログラムの回復不能なデータ損失につながる可能性があることに注意してください。これも望ましくない場合があります。

スマート ポインターを返す: 常に値渡し

呼び出し元が使用するために作成されたオブジェクトを指していると思われるスマート ポインターを返すことについて一言で締めくくります。これは、ポインタを関数に渡す場合と実際には比較できるケースではありませんが、完全を期すために、そのような場合は常に値で返すことを主張したいと思います(ステートメントでは使用しないでください)。 多分ちょうど nix されたばかりのポインターへの参照を取得したいと思う人はいません。std::movereturn

于 2014-06-26T10:47:46.433 に答える
5

編集:厳密に言えば、コードは機能しますが、この答えは間違っています。その下の議論があまりにも有用であるため、ここに残しておきます。この他の答えは、私がこれを最後に編集したときに与えられた最良の答えです。unique_ptr引数をコンストラクターまたは関数に渡すにはどうすればよいですか?

の基本的な考え方::std::moveは、あなたを追い越している人々は、彼らが追い越しをしていることが所有権を失うことunique_ptrを知っているという知識を表現するためにそれを使用するべきであるということです。unique_ptr

unique_ptrこれは、メソッド自体ではなく、メソッドでaへの右辺値参照を使用する必要があることを意味しますunique_ptr。プレーンオールドを渡すにはコピーを作成する必要があるため、これはとにかく機能しません。これはunique_ptr、のインターフェイスで明示的に禁止されていますunique_ptr。興味深いことに、名前付き右辺値参照を使用すると、それが再び左辺値に戻るため、メソッド::std::move でも使用する必要があります。

これは、2つのメソッドが次のようになることを意味します。

Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability

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

次に、メソッドを使用する人々はこれを行います:

Base::UPtr objptr{ new Base; }
Base::UPtr objptr2{ new Base; }
Base fred(::std::move(objptr)); // objptr now loses ownership
fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership

ご覧のとおり、は::std::move、ポインタが最も関連性が高く、知るのに役立つ時点で所有権を失うことを表しています。これが目に見えない形で起こった場合、あなたのクラスを使用している人々がobjptr、すぐに明らかな理由もなく突然所有権を失うことは非常に混乱するでしょう。

于 2011-11-13T20:29:35.067 に答える
4

unique_ptrはい、コンストラクターで by 値を使用する場合は、そうする必要があります。明快さは素晴らしいことです。unique_ptrはコピー不可 (private copy ctor) であるため、記述した内容によってコンパイル エラーが発生するはずです。

于 2011-11-13T20:06:54.450 に答える
0
Base(Base::UPtr n):next(std::move(n)) {}

としてはるかに良いはずです

Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}

void setNext(Base::UPtr n)

する必要があります

void setNext(Base::UPtr&& n)

同じ体で。

そして...何がevt入っていますかhandle()??

于 2011-11-13T20:08:30.490 に答える