web-dev-qa-db-ja.com

ポインタ変数を移動してもnullに設定されないのはなぜですか?

ムーブコンストラクターとムーブ代入演算子を実装する場合、多くの場合、次のようなコードを記述します。

_p = other.p;
other.p = 0;
_

暗黙的に定義された移動操作は、次のようなコードで実装されます。

_p = std::move(other.p);
_

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

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

43
fredoverflow

移動後に生のポインタをnullに設定すると、ポインタが所有権を表すことになります。ただし、関係を表すために多くのポインターが使用されます。さらに、長い間、生のポインタを使用する場合とは異なる所有権関係の表現をお勧めします。たとえば、参照している所有権関係はstd::unique_ptr<T>で表されます。暗黙的に生成された移動操作で所有権を処理したい場合は、必要な所有権の動作を実際に表す(および実装する)メンバーを使用するだけです。

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

34
Dietmar Kühl

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

私はその答えは次のとおりだと思います。そのような動作を自分で実装するのは非常に簡単なので、標準はコンパイラ自体にルールを課す必要はないと感じていました。 C++言語は巨大であり、使用前にすべてを想像できるわけではありません。たとえば、C++のテンプレートを見てみましょう。これは、今日の使用方法(つまり、メタプログラミング機能)で使用するように設計されたものではありません。したがって、標準は自由を与えるだけで、std::move(other.p)に対して特定のルールを作成しなかったと思います。その1つは設計原理です: " t使用しないものに対して支払います "

ただし、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_ptrnotスマートポインタであるため、コピーセマンティクスとデストラクタを作成する必要があることに注意してください。

4
Nawaz

ここでの違いは、一方で完全に吹き飛ばされたオブジェクトであり、他方でPODであると思います。

  • オブジェクトの場合、インプリメンターは、移動構築と移動割り当てが行うべきことを指定するか、コンパイラーがデフォルトを生成します。デフォルトでは、すべてのメンバーの移動コンストラクター/割り当て演算子を呼び出します。
  • POD(およびポインターはPOD)の場合、C++はCから継承され、明示的にコーディングされていない場合、C++は何も行われません。これは、クラスのPODメンバーがコンストラクターで処理されるのと同じ動作です。それらを明示的にイニシャライザリストに追加しないと、初期化されないままになり、潜在的なバグの原因になります。私の知る限り、これはコンパイラが生成したコンストラクタに対しても有効です!そのため、私は一般的にすべてのメンバーを安全な側に初期化する習慣をつけました。
0
Don Pedro

たとえば、共有オブジェクトへのポインタがある場合です。オブジェクトを移動した後は、内部的に一貫した状態を維持する必要があるため、nullにしてはならないポインタをnull値に設定することは正しくないことに注意してください。

つまり:

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

編集

標準からのMoveConstructibeに関する引用は次のとおりです。

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
doublep