Apakah penghapusan ini diizinkan?

232

Apakah boleh delete this;jika pernyataan delete adalah pernyataan terakhir yang akan dieksekusi pada instance kelas itu? Tentu saja saya yakin bahwa objek yang diwakili oleh this-pointer newdibuat.

Saya sedang memikirkan sesuatu seperti ini:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Bisakah saya melakukan ini?

Martijn Courteaux
sumber
13
Masalah utama adalah bahwa jika Anda delete thistelah membuat kopling ketat antara kelas dan metode alokasi yang digunakan untuk membuat objek dari kelas itu. Itu adalah desain OO yang sangat buruk, karena hal yang paling mendasar dalam OOP adalah membuat kelas otonom yang tidak tahu atau peduli tentang apa yang dilakukan penelepon mereka. Dengan demikian kelas yang dirancang dengan baik seharusnya tidak tahu atau peduli tentang bagaimana itu dialokasikan. Jika Anda karena suatu alasan memerlukan mekanisme yang aneh, saya pikir desain yang lebih baik adalah dengan menggunakan kelas pembungkus di sekitar kelas yang sebenarnya, dan biarkan pembungkus berurusan dengan alokasi.
Lundin
Tidak bisakah Anda menghapus setWorkingModule?
Jimmy T.

Jawaban:

238

C ++ FAQ Lite memiliki entri khusus untuk ini

Saya pikir kutipan ini merangkumnya dengan baik

Selama Anda berhati-hati, tidak apa-apa jika objek melakukan bunuh diri (hapus ini).

JaredPar
sumber
15
FQA yang sesuai juga memiliki beberapa komentar yang berguna: yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.
1
Untuk keselamatan Anda dapat menggunakan destruktor pribadi pada objek asli untuk memastikan tidak dibangun di atas tumpukan atau sebagai bagian dari array atau vektor.
Cem Kalyoncu
Tentukan 'hati-hati'
CinCout
3
'Hati-hati' didefinisikan dalam artikel FAQ yang ditautkan. (Sementara tautan FQA sebagian besar mengoceh - seperti hampir semua yang ada di dalamnya - seberapa buruk C ++)
CharonX
88

Ya, delete this;telah menetapkan hasil, selama (seperti yang telah Anda catat), Anda memastikan objek dialokasikan secara dinamis, dan (tentu saja) tidak pernah mencoba menggunakan objek setelah dihancurkan. Selama bertahun-tahun, banyak pertanyaan telah diajukan tentang apa yang standar katakan secara spesifik delete this;, sebagai lawan menghapus beberapa pointer lainnya. Jawabannya cukup singkat dan sederhana: tidak banyak bicara. Itu hanya mengatakan bahwa deleteoperan harus berupa ekspresi yang menunjuk pointer ke objek, atau array objek. Ini masuk ke sedikit detail tentang hal-hal seperti bagaimana mencari tahu apa fungsi deletedeallokasi (jika ada) untuk memanggil untuk melepaskan memori, tetapi seluruh bagian pada (§ [expr.delete]) tidak menyebutkan delete this;secara khusus sama sekali. Bagian tentang penghancur menyebutkandelete this di satu tempat (§ [class.dtor] / 13):

Pada titik definisi destruktor virtual (termasuk definisi implisit (15,8)), fungsi deallokasi non-array ditentukan seolah-olah untuk ekspresi hapus ini muncul dalam destruktor non-virtual kelas destruktor (lihat 8.3.5 ).

Itu cenderung mendukung gagasan yang dianggap standar sebagai delete this;valid - jika tidak valid, tipenya tidak akan berarti. Itu satu-satunya tempat yang disebutkan standar delete this;sama sekali, sejauh yang saya tahu.

Bagaimanapun, beberapa orang menganggap delete thisperetasan tidak baik, dan memberi tahu siapa pun yang akan mendengarkan bahwa itu harus dihindari. Satu masalah yang sering dikutip adalah kesulitan memastikan bahwa objek kelas hanya dialokasikan secara dinamis. Orang lain menganggapnya sebagai ungkapan yang sangat masuk akal, dan menggunakannya sepanjang waktu. Secara pribadi, saya di suatu tempat di tengah: Saya jarang menggunakannya, tetapi jangan ragu untuk melakukannya ketika itu tampaknya menjadi alat yang tepat untuk pekerjaan itu.

Waktu utama Anda menggunakan teknik ini adalah dengan objek yang memiliki kehidupan yang hampir seluruhnya miliknya. Satu contoh yang dikutip oleh James Kanze adalah sistem penagihan / pelacakan yang ia kerjakan untuk perusahaan telepon. Ketika Anda mulai membuat panggilan telepon, ada yang memperhatikan hal itu dan membuat phone_callobjek. Sejak saat itu, phone_callobjek menangani detail panggilan telepon (membuat koneksi saat Anda melakukan panggilan, menambahkan entri ke database untuk mengatakan kapan panggilan dimulai, mungkin menghubungkan lebih banyak orang jika Anda melakukan panggilan konferensi, dll.) orang terakhir yang dipanggil menutup telepon, phone_callobjek melakukan pembukuan terakhirnya (mis., menambahkan entri ke database untuk mengatakan kapan Anda menutup telepon, sehingga mereka dapat menghitung berapa lama panggilan Anda) dan kemudian menghancurkan dirinya sendiri. Seumur hidupphone_callobjek didasarkan pada ketika orang pertama memulai panggilan dan ketika orang terakhir meninggalkan panggilan - dari sudut pandang sisa sistem, itu pada dasarnya sepenuhnya arbitrer, jadi Anda tidak dapat mengikatnya ke lingkup leksikal dalam kode , atau apa pun di pesanan itu.

Bagi siapa pun yang mungkin peduli tentang seberapa dapat diandalkannya pengkodean semacam ini: jika Anda melakukan panggilan telepon ke, dari, atau melalui hampir semua bagian Eropa, ada peluang yang cukup bagus bahwa kode itu ditangani (setidaknya sebagian) dengan kode itu tidak persis seperti ini.

Jerry Coffin
sumber
2
Terima kasih, saya akan letakkan di suatu tempat di memori saya. Saya kira Anda mendefinisikan konstruktor dan destruktor sebagai pribadi dan menggunakan beberapa metode pabrik statis untuk membuat objek seperti itu.
Alexandre C.
@Alexandre: Anda mungkin akan melakukan itu dalam banyak kasus - saya tidak tahu di mana pun dekat dengan semua detail sistem yang sedang dikerjakannya, jadi saya tidak bisa mengatakan dengan pasti tentang hal itu.
Jerry Coffin
Cara saya sering mengatasi masalah bagaimana memori dialokasikan adalah dengan memasukkan bool selfDeleteparameter dalam konstruktor yang ditugaskan ke variabel anggota. Memang, ini berarti menyerahkan tali programmer cukup untuk mengikat tali di dalamnya, tapi saya menemukan bahwa lebih baik kebocoran memori.
MBraedley
1
@ MBraedley: Saya sudah melakukan hal yang sama, tetapi lebih memilih untuk menghindari apa yang menurut saya seperti lumpur.
Jerry Coffin
Bagi siapa pun yang mungkin peduli ... ada peluang yang cukup bagus untuk ditangani (setidaknya sebagian) dengan kode yang tepat this. Ya, kode tersebut ditangani dengan tepat this. ;)
Galaxy
46

Jika itu membuat Anda takut, ada peretasan legal:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Saya pikir delete thisini C ++ idiomatis, dan saya hanya menyajikan ini sebagai rasa ingin tahu.

Ada kasus di mana konstruksi ini sebenarnya berguna - Anda dapat menghapus objek setelah melemparkan pengecualian yang membutuhkan data anggota dari objek. Objek tetap valid sampai setelah lemparan berlangsung.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Catatan: jika Anda menggunakan kompiler yang lebih tua dari C ++ 11 yang dapat Anda gunakan std::auto_ptrsebagai gantinya std::unique_ptr, itu akan melakukan hal yang sama.

Mark tebusan
sumber
Saya tidak bisa mendapatkan ini untuk dikompilasi menggunakan c ++ 11, apakah ada beberapa opsi kompiler khusus untuk itu? Juga tidak memerlukan pemindahan pointer ini?
Burung Hantu
@ Ow tidak yakin apa yang Anda maksud, itu berfungsi untuk saya: ideone.com/aavQUK . Membuat unique_ptrdari yang lain unique_ptr membutuhkan gerakan, tetapi bukan dari pointer mentah. Kecuali hal-hal berubah dalam C ++ 17?
Mark Ransom
Ahh C ++ 14, itu sebabnya. Saya perlu memperbarui c ++ saya di kotak dev saya. Saya akan coba lagi malam ini di sistem gentoo saya yang baru muncul!
Burung hantu
25

Salah satu alasan mengapa C ++ dirancang adalah untuk membuatnya mudah untuk menggunakan kembali kode. Secara umum, C ++ harus ditulis sehingga berfungsi apakah kelas dipakai pada heap, dalam array, atau pada stack. "Hapus ini" adalah praktik pengkodean yang sangat buruk karena hanya akan berfungsi jika satu instance didefinisikan pada heap; dan lebih baik tidak ada pernyataan penghapusan lain, yang biasanya digunakan oleh sebagian besar pengembang untuk membersihkan tumpukan. Melakukan hal ini juga mengasumsikan bahwa tidak ada programmer pemeliharaan di masa depan yang akan menyembuhkan kebocoran memori yang dirasakan salah dengan menambahkan pernyataan hapus.

Bahkan jika Anda tahu sebelumnya bahwa rencana Anda saat ini adalah hanya mengalokasikan satu instance pada heap, bagaimana jika beberapa pengembang happy-go-lucky datang di masa depan dan memutuskan untuk membuat instance di stack? Atau, bagaimana jika dia memotong dan menempel bagian-bagian tertentu dari kelas ke kelas baru yang ingin dia gunakan di tumpukan? Ketika kode mencapai "hapus ini" itu akan pergi dan menghapusnya, tetapi kemudian ketika objek keluar dari ruang lingkup, itu akan memanggil destruktor. Destructor kemudian akan mencoba untuk menghapusnya lagi dan kemudian Anda disemprot. Di masa lalu, melakukan sesuatu seperti ini akan mengacaukan tidak hanya program tetapi sistem operasi dan komputer perlu di-boot ulang. Bagaimanapun, ini sangat TIDAK dianjurkan dan hampir selalu harus dihindari. Saya harus putus asa, terpampang serius,

Bob Bryan
sumber
7
+1. Saya tidak mengerti mengapa Anda tidak dipilih. "C ++ harus ditulis sehingga bisa berfungsi apakah kelas dipakai pada heap, dalam array, atau pada stack" adalah saran yang sangat bagus.
Joh
1
Anda bisa membungkus objek yang ingin Anda hapus sendiri dalam kelas khusus yang menghapus objek dan kemudian itu sendiri, dan menggunakan teknik ini untuk mencegah alokasi tumpukan: stackoverflow.com/questions/124880/… Ada saat-saat ketika sebenarnya tidak ada alternatif yang layak. Saya hanya menggunakan teknik ini untuk menghapus sendiri utas yang dimulai oleh fungsi DLL, tetapi fungsi DLL harus kembali sebelum utas berakhir.
Felix Dombek
Anda tidak dapat memprogram sedemikian rupa sehingga seseorang yang melakukan salin dan tempel dengan kode Anda akhirnya menyalahgunakannya
Jimmy T.
22

Itu diperbolehkan (hanya jangan menggunakan objek setelah itu), tapi saya tidak akan menulis kode seperti itu pada praktek. Saya berpikir bahwa delete thisakan muncul hanya dalam fungsi yang disebut releaseatau Releasedan terlihat seperti: void release() { ref--; if (ref<1) delete this; }.

Kirill V. Lyadvinsky
sumber
Yang persis sekali di setiap proyek saya ... :-)
cmaster - mengembalikan monica
15

Nah, dalam delete thiskonstruksi Component Object Model (COM) dapat menjadi bagian dari Releasemetode yang dipanggil setiap kali Anda ingin melepaskan objek yang didapat:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
UnknownGosu
sumber
8

Ini adalah idiom inti untuk objek yang dihitung referensi.

Penghitungan referensi adalah bentuk yang kuat dari pengumpulan sampah deterministik - ini memastikan objek mengelola masa hidupnya SENDIRI alih-alih mengandalkan petunjuk 'pintar', dll. Untuk melakukannya untuk mereka. Objek yang mendasarinya hanya pernah diakses melalui pointer pintar "Referensi", yang dirancang sedemikian rupa sehingga pointer menambah dan mengurangi bilangan bulat anggota (jumlah referensi) dalam objek yang sebenarnya.

Ketika referensi terakhir turun dari tumpukan atau dihapus, jumlah referensi akan menjadi nol. Perilaku default objek Anda kemudian akan menjadi panggilan untuk "menghapus ini" untuk mengumpulkan sampah - perpustakaan yang saya tulis memberikan panggilan "CountIsZero" virtual yang dilindungi di kelas dasar sehingga Anda dapat menimpa perilaku ini untuk hal-hal seperti caching.

Kunci untuk membuat ini aman adalah tidak memungkinkan pengguna mengakses KONSTRUKTOR objek yang bersangkutan (membuatnya terlindungi), tetapi membuat mereka memanggil beberapa anggota statis - seperti "static Reference CreateT (...)" seperti PABRIK. Dengan begitu Anda TAHU pasti bahwa mereka selalu dibangun dengan "baru" biasa dan bahwa tidak ada pointer mentah yang pernah tersedia, jadi "hapus ini" tidak akan pernah meledak.

Zack Yezek
sumber
Mengapa Anda tidak bisa hanya memiliki kelas "pengalokasi / pengumpul sampah" (antarmuka) kelas, sebuah antarmuka di mana semua alokasi dilakukan dan biarkan kelas itu menangani semua penghitungan referensi dari objek yang dialokasikan? Alih-alih memaksa benda itu sendiri untuk repot dengan tugas pengumpulan sampah, sesuatu yang sama sekali tidak terkait dengan tujuan yang ditentukan.
Lundin
1
Anda juga bisa membuat destruktor dilindungi untuk melarang alokasi statis dan tumpukan objek Anda.
Game_Overture
7

Anda bisa melakukannya. Namun, Anda tidak dapat menetapkan ini. Jadi alasan Anda menyatakan untuk melakukan ini, "Saya ingin mengubah pandangan," tampaknya sangat dipertanyakan. Metode yang lebih baik, menurut saya, adalah untuk objek yang memegang tampilan untuk menggantikan tampilan itu.

Tentu saja, Anda menggunakan objek RAII sehingga Anda tidak perlu memanggil delete sama sekali ... benar?

Edward Strange
sumber
4

Ini adalah pertanyaan lama, dijawab, tetapi @Alexandre bertanya, "Mengapa ada orang yang mau melakukan ini?", Dan saya pikir saya mungkin memberikan contoh penggunaan yang saya pertimbangkan sore ini.

Kode Warisan. Menggunakan pointer telanjang Obj * obj dengan menghapus obj di akhir.

Sayangnya saya kadang-kadang perlu, tidak sering, untuk menjaga objek tetap hidup.

Saya mempertimbangkan untuk menjadikannya sebagai penghitung smart pointer. Tetapi akan ada banyak kode untuk diubah, jika saya harus menggunakannya di ref_cnt_ptr<Obj>mana-mana. Dan jika Anda mencampur Obj * dan ref_cnt_ptr telanjang, Anda dapat menghapus objek secara implisit ketika ref_cnt_ptr terakhir hilang, meskipun ada Obj * masih hidup.

Jadi saya berpikir untuk membuat eksplisit_delete_ref_cnt_ptr. Yaitu sebuah referensi penghitung referensi di mana penghapusan hanya dilakukan dalam rutinitas penghapusan eksplisit. Menggunakannya di satu tempat di mana kode yang ada mengetahui umur objek, serta dalam kode baru saya yang membuat objek tetap hidup lebih lama.

Menambah dan mengurangi jumlah referensi sebagai eksplisit_delete_ref_cnt_ptr bisa dimanipulasi.

Tapi JANGAN membebaskan ketika jumlah referensi dianggap nol dalam destruktor eksplisit_delete_ref_cnt_ptr.

Hanya membebaskan ketika jumlah referensi dianggap nol dalam operasi penghapusan seperti eksplisit. Misalnya dalam sesuatu seperti:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

Oke, kira-kira seperti itu. Agak tidak biasa untuk memiliki referensi tipe pointer yang dihitung tidak secara otomatis menghapus objek yang ditunjuk di destruktor ptr rc'ed. Tapi sepertinya ini mungkin membuat campuran pointer telanjang dan pointer rc sedikit lebih aman.

Namun sejauh ini tidak perlu untuk menghapus ini.

Tapi kemudian terlintas di benak saya: jika objek menunjuk, orang yang dituju, tahu bahwa itu sedang dihitung referensi, misalnya jika jumlah berada di dalam objek (atau dalam beberapa tabel lain), maka delete_if_rc0 rutin bisa menjadi metode dari objek pointee, bukan pointer (pintar).

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

Sebenarnya, itu tidak perlu menjadi metode anggota sama sekali, tetapi bisa menjadi fungsi bebas:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(BTW, saya tahu kode ini kurang tepat - menjadi kurang dapat dibaca jika saya menambahkan semua detail, jadi saya membiarkannya seperti ini.)

Krazy Glew
sumber
0

Hapus ini legal selama objek berada di heap. Anda perlu meminta objek menjadi tumpukan saja. Satu-satunya cara untuk melakukan itu adalah membuat destruktor dilindungi - cara ini bisa saja dihapus HANYA dari kelas, jadi Anda membutuhkan metode yang akan memastikan penghapusan.

Swift - Friday Pie
sumber