2012-02-26 11 views
29

移動コンストラクタと移動代入演算子を実装し、1は、多くの場合、このようなコードを書いている: ポインタ変数を移動するとnullに設定されないのはなぜですか?

p = other.p; 
other.p = 0; 

暗黙的に定義された移動操作

は、このようなコードで実装されます:

間違っているだろう
p = std::move(other.p); 

、ポインター変数を移動するとでなく、にnullが設定されているためです。何故ですか?移動操作で元のポインタ変数を変更しないままにしたい場合はありますか?

注:「移動」することで、私はだけの部分式std::move(other.p)を意味するものではありません、私は式全体p = std::move(other.p)を意味します。だから、なぜ「割り当ての右辺がポインタxvalueなら、割り当てが行われた後にnullに設定される」という特別な言語規則はないのですか?

+5

なぜでしょうか?あなたが「動かされた」オブジェクトで行うべき唯一のことは、それを無視することです。あなたがもはやポインタを使用しない場合は、クラスが所有していないメモリを指すポインタは問題ではないでしょうか? – hvd

+5

「あなたが使っていないものを支払うことはありません」 –

+7

@hvd:デストラクタは、 'delete p'と言ってもそれを無視しません:) – fredoverflow

答えて

27

struct foo 
{ 
    bar* shared_factory; // This can be the same for several 'foo's 
         // and must never null. 
}; 

編集は、ここで

は、標準からの引用についてMoveConstructibeです所有権を表します。しかし、多くのポインタが関係を表すために使用されます。さらに、長い間、所有関係は生ポインタを使用する場合とは異なって表現されることが推奨されます。たとえば、参照している所有権関係はstd::unique_ptr<T>で表されます。暗黙的に生成された移動操作で所有権を守るためには、実際に必要な所有権の振る舞いを表現(実装)するメンバーを使用するだけです。

また、生成された移動操作の動作は、コピー操作で行われた動作と一致しています。また、所有権の前提もしていません。ポインタがコピーされている場合はディープコピー。これを実現するには、関連するセマンティクスをエンコードする適切なクラスを作成する必要もあります。

+3

私は、「移動することは決してコピーよりも高価にならない」と言った紙を読んでいたことを思い出しました。元のポインタnullを設定すると、そのルールが破られます。私が話している論文を知っていますか、それとも私の脳はそれを作りましたか? – fredoverflow

+1

+1。良い説明。私よりもよかった! – Nawaz

4

私は答えがそうだと思います。このような振る舞いを自分で実装するのはかなり簡単なので、Standardはコンパイラ自体に何らかのルールを課す必要はないと感じました。 C++言語は膨大で、使用する前にすべてを想像することはできません。たとえば、C++のテンプレートを考えてみましょう。それは、今日使用されている方法で最初に使用されるようには設計されていません(つまり、メタプログラミング能力です)。だから、スタンダードはちょうど自由を与え、std::move(other.p)のための特別なルールを作っていないと思っています。それは設計原則の1つで、"あなたはあなたが使っていないものを支払うことはありません"です。

std::unique_ptrは移動可能ですが、コピーできません。あなたが望むのであれば、ポインタ、セマンティックの両方の移動とコピー可能であるし、ここでは1つの些細な実装です:

template<typename T> 
struct movable_ptr 
{ 
    T *pointer; 
    movable_ptr(T *ptr=0) : pointer(ptr) {} 
    movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; } 
    movable_ptr(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
    } 
    movable_ptr<T>& operator=(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
     return *this; 
    } 
    T* operator->() const { return pointer; } 
    T& operator*() const { return *pointer; } 

    movable_ptr(movable_ptr<T> const & other) = default; 
    movable_ptr<T> & operator=(movable_ptr<T> const & other) = default; 
}; 

今、あなたはあなた自身の移動・セマンティクスを記述することなく、クラスを書くことができます。

struct T 
{ 
    movable_ptr<A> aptr; 
    movable_ptr<B> bptr; 
    //... 

    //and now you could simply say 
    T(T&&) = default; 
    T& operator=(T&&) = default; 
}; 

注意をmovable_ptrではなく、スマートポインタであるので、コピーセマンティクスとデストラクタを書く必要があります。

+0

しかし、暗黙的に定義されたmoveオペレーションは 'move_Ptr'を呼び出さないでしょう... – fredoverflow

+3

@Fred - 生ポインタを格納するのではなく、十分にスマートなポインタを使用するとします。 *それは問題だろうか? –

+0

@BoPersson:私はそれが答えでなければならないと思いますか? – Nawaz

0

たとえば、共有オブジェクトへのポインタがある場合。移動後、オブジェクトは内部的に一貫性のある状態のままでなければならないことを忘れないでください。つまり、nullでないポインタをnull値に設定することは正しくありません。

すなわち:それはポインタのことを意味し移動した後にnullに生のポインタを設定する

T u = rv; 
... 
rv’s state is unspecified [ Note:rv must still meet the requirements 
of the library component that is using it. The operations listed in 
those requirements must work as specified whether rv has been moved 
from or not. 
+0

しかし、ポインタ変数をnullに設定するのはxvaluesでしか起こりません。クライアントはその後、ヌルポインタを検査することができません。 – fredoverflow

+0

デストラクタがポインタを何らかの形で使用し、NULLの場合はUBを呼び出すとどうなりますか?また、私はクレームをスタンダードから引用することはできませんが、覚えている限り、移動可能なオブジェクトは破壊的ではなく、有効にしておく必要があります。 – doublep

+0

@FredOverflow:標準で関連する見積もりが見つかりました。 – doublep

5

移動は、移動元オブジェクトを「無効」にレンダリングします。それはではありません。は自動的に安全な「空の」状態に設定されます。 C++の長年の原則 "あなたが使っていないものを支払うことはありません"に従って、あなたが望むならそれはあなたの仕事です。

関連する問題