Pertanyaan Mengapa memindahkan variabel penunjuk tidak disetel ke null?


Ketika mengimplementasikan konstruktor bergerak dan memindahkan operator penugasan, seseorang sering menulis kode seperti ini:

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

Operasi pemindahan yang ditentukan secara implisit akan diimplementasikan dengan kode seperti ini:

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

Yang salah, karena memindahkan variabel penunjuk tidak tidak setel ke null. Mengapa demikian? Apakah ada kasus yang kami ingin operasi pemindahan meninggalkan variabel penunjuk asli tidak berubah?

Catatan: Dengan "bergerak", saya lakukan tidak hanya berarti subekspresi std::move(other.p), Maksud saya seluruh ekspresi p = std::move(other.p). Jadi, mengapa tidak ada aturan bahasa khusus yang mengatakan "Jika sisi kanan tugas adalah pointer xvalue, itu diatur ke nol setelah penugasan telah terjadi."?


32
2018-02-26 10:55


asal


Jawaban:


Menetapkan pointer mentah ke nol setelah pemindahan menyiratkan bahwa penunjuk mewakili kepemilikan. Namun, banyak pointer digunakan untuk merepresentasikan hubungan. Selain itu, untuk waktu yang lama direkomendasikan bahwa hubungan kepemilikan diwakili berbeda daripada menggunakan pointer mentah. Misalnya, hubungan kepemilikan yang Anda maksud diwakili oleh std::unique_ptr<T>. Jika Anda ingin operasi pemindahan yang dilakukan secara implisit mengurus kepemilikan Anda, yang perlu Anda lakukan adalah menggunakan anggota yang benar-benar mewakili (dan menerapkan) perilaku kepemilikan yang diinginkan.

Selain itu, perilaku operasi pemindahan yang dihasilkan konsisten dengan apa yang telah dilakukan dengan operasi salinan: mereka juga tidak membuat asumsi kepemilikan dan tidak melakukan mis. salinan yang dalam jika pointer disalin. Jika Anda ingin ini terjadi, Anda juga perlu membuat kelas yang sesuai dengan pengkodean semantik yang relevan.


28
2018-02-26 13:08



Pindah menjadikan objek yang dipindahkan dari "tidak valid". Itu benar tidak secara otomatis mengaturnya ke keadaan "kosong" yang aman. Sesuai dengan prinsip lama C ++ tentang "jangan bayar untuk apa yang tidak Anda gunakan", itulah pekerjaan Anda jika Anda menginginkannya.


5
2018-02-26 14:46



Saya kira jawabannya adalah: menerapkan perilaku seperti itu sendiri cukup sepele dan karenanya Standar tidak merasa perlu memberlakukan aturan apa pun pada compiler itu sendiri. Bahasa C ++ sangat besar dan tidak semuanya bisa dibayangkan sebelum digunakan. Ambil contoh, templat C ++. Itu tidak dirancang untuk digunakan seperti sekarang ini (yaitu kemampuan metaprogramming). Jadi saya pikir, Standar hanya memberi kebebasan, dan tidak membuat aturan khusus untuk itu std::move(other.p), mengikuti salah satu prinsip desainnya: "Kamu tidak membayar untuk apa yang tidak kamu gunakan".

Meskipun, std::unique_ptr dapat dipindahkan, meskipun tidak dapat dipinjam. Jadi jika Anda ingin pointer-semantik yang dapat digerakkan dan dapat dipindah keduanya, maka di sini adalah satu implementasi sepele:

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;
};

Sekarang Anda dapat menulis kelas, tanpa menulis gerakan semantik Anda sendiri:

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

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

Perhatikan bahwa Anda masih harus menulis copy-semantik dan destructor, seperti movable_ptr aku s tidak pointer pintar.


4
2018-02-26 11:01



Misalnya, jika Anda memiliki penunjuk ke objek bersama. Ingat, bahwa setelah memindahkan objek harus tetap dalam keadaan yang konsisten secara internal, jadi pengaturan penunjuk yang tidak boleh nol ke nilai nol tidak benar.

Yaitu.:

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

Edit

Ini kutipan tentangnya MoveConstructibe dari standar:

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
2018-02-26 11:02