Mengapa destruktor tidak dipanggil pada operator hapus?

16

Saya mencoba menelepon ::deleteuntuk kelas di operator deletedalamnya. Tetapi destruktor tidak disebut.

Saya mendefinisikan kelas MyClassyang operator deletekelebihan beban. Global operator deletejuga kelebihan beban. The kelebihan beban operator deletedari MyClassakan memanggil global kelebihan beban operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

Outputnya adalah:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Sebenarnya:

Hanya ada satu panggilan untuk destructor sebelum memanggil kelebihan beban operator deletedari MyClass.

Diharapkan:

Ada dua panggilan ke destructor. Salah satu sebelum memanggil kelebihan beban operator deletedari MyClass. Lain sebelum memanggil global operator delete.

expinc
sumber
6
MyClass::operator new()harus mengalokasikan memori mentah, dari (setidaknya) sizebyte. Seharusnya tidak berusaha untuk sepenuhnya membangun contoh MyClass. Konstruktor MyClassdieksekusi setelah MyClass::operator new(). Kemudian, deleteekspresi di main()memanggil destruktor, dan melepaskan memori (tanpa memanggil destruktor lagi). The ::delete pekspresi tidak memiliki informasi tentang jenis objek ppoin di, karena pmerupakan void *, sehingga tidak bisa memanggil destructor.
Peter
Terkait: stackoverflow.com/a/8918942/845092
Mooing Duck
2
Respons yang sudah diberikan kepada Anda benar, tetapi saya bertanya-tanya: mengapa Anda mencoba mengganti yang baru dan menghapus? Kasus penggunaan yang umum adalah menerapkan manajemen memori khusus (GC, memori yang tidak berasal dari malloc default (), dll). Mungkin Anda menggunakan alat yang salah untuk apa yang ingin Anda capai.
noamtm
2
::delete p;menyebabkan perilaku tidak terdefinisi karena jenis *ptidak sama dengan jenis objek yang dihapus (atau kelas dasar dengan penghancur virtual)
MM
@ MM Kompiler utama hanya memperingatkannya paling banyak, jadi saya tidak menyadari bahwa void*operan bahkan secara eksplisit salah bentuk. [expr.delete] / 1 : " Operand harus dari penunjuk ke tipe objek atau tipe kelas. [...] Ini menyiratkan bahwa suatu objek tidak dapat dihapus menggunakan penunjuk tipe batal karena batal bukan tipe objek. * "@OP Saya telah mengubah jawaban saya.
kenari

Jawaban:

17

Anda menyalahgunakan operator newdan operator delete. Operator-operator ini adalah fungsi alokasi dan deallokasi. Mereka tidak bertanggung jawab untuk membangun atau merusak objek. Mereka bertanggung jawab hanya untuk menyediakan memori di mana objek akan ditempatkan.

Versi global dari fungsi-fungsi ini adalah ::operator newdan ::operator delete. ::newdan ::deleteyang baru / hapus-ekspresi, seperti new/ delete, berbeda dari yang, dalam hal itu ::newdan ::deleteakan memotong kelas-spesifik operator new/ operator deleteoverload.

Konstruksi baru / hapus-ekspresi / destruksi dan alokasikan / alokasikan (dengan memanggil yang sesuai operator newatau operator deletesebelum konstruksi atau setelah kehancuran).

Karena kelebihan Anda hanya bertanggung jawab untuk bagian alokasi / deallokasi, itu harus memanggil ::operator newdan ::operator deletebukannya ::newdan ::delete.

The deletedalam delete myClass;bertanggung jawab untuk memanggil destructor.

::delete p;tidak memanggil destruktor karena pmemiliki tipe void*dan oleh karena itu ekspresi tidak dapat mengetahui destruktor apa yang harus dipanggil. Mungkin akan memanggil Anda diganti ::operator deleteuntuk membatalkan alokasi memori, meskipun menggunakan void*operan as untuk menghapus-ekspresi tidak terbentuk (lihat edit di bawah).

::new MyClass();panggilan Anda diganti ::operator newuntuk mengalokasikan memori dan membangun objek di dalamnya. Pointer ke objek ini dikembalikan void*ke ekspresi-baru di MyClass* myClass = new MyClass();, yang kemudian akan membangun objek lain dalam memori ini, mengakhiri masa pakai objek sebelumnya tanpa memanggil destruktornya.


Edit:

Berkat komentar @ MM pada pertanyaan itu, saya menyadari bahwa void*operan as to not bad ::deleteformed. ( [expr.delete] / 1 ) Namun, kompiler utama tampaknya memutuskan untuk hanya memperingatkan tentang ini, bukan kesalahan. Sebelum itu dibuat sakit-terbentuk, menggunakan ::deletepada void*perilaku yang sudah terdefinisi punya, lihat pertanyaan ini .

Oleh karena itu, program Anda rusak dan Anda tidak memiliki jaminan bahwa kode tersebut benar-benar melakukan apa yang saya jelaskan di atas jika masih berhasil dikompilasi.


Seperti yang ditunjukkan oleh @SanderDeDycker di bawah jawabannya, Anda juga memiliki perilaku tidak terdefinisi karena dengan membangun objek lain di memori yang sudah berisi MyClassobjek tanpa memanggil destruktor objek itu terlebih dahulu Anda melanggar [basic.life] / 5 yang melarang melakukannya jika Program tergantung pada efek samping destruktor. Dalam hal ini printfpernyataan dalam destruktor memiliki efek samping seperti itu.

kenari
sumber
Penyalahgunaan dimaksudkan untuk memeriksa cara kerja operator ini. Namun, terima kasih atas jawaban Anda. Tampaknya satu-satunya jawaban yang memecahkan masalah saya.
expinc
13

Kelebihan khusus kelas Anda dilakukan dengan tidak benar. Ini dapat dilihat pada output Anda: konstruktor dipanggil dua kali!

Di kelas khusus operator new, hubungi operator global secara langsung:

return ::operator new(size);

Demikian pula, di kelas khusus operator delete, lakukan:

::operator delete(p);

Lihat operator newhalaman referensi untuk lebih jelasnya.

Sander De Dycker
sumber
Saya tahu konstruktor dipanggil dua kali dengan memanggil :: baru di operator baru dan saya sungguh-sungguh. Pertanyaan saya adalah mengapa destructor tidak dipanggil saat memanggil :: delete di operator delete?
expinc
1
@ expinc: Sengaja memanggil konstruktor untuk kedua kalinya tanpa memanggil destruktor terlebih dahulu adalah ide yang sangat buruk. Untuk destruktor non-sepele (seperti milik Anda), Anda bahkan merambah ke wilayah perilaku yang tidak terdefinisi (jika Anda bergantung pada efek samping destruktor, yang Anda lakukan) - ref. [basic.life] §5 . Jangan lakukan ini.
Sander De Dycker
1

Lihat Referensi CPP :

operator delete, operator delete[]

Deallocate storage yang sebelumnya dialokasikan oleh pencocokan operator new. Fungsi-fungsi deallokasi ini disebut dengan delete-expressions dan oleh new-expressions untuk mendeallocate memori setelah merusak (atau gagal membangun) objek dengan durasi penyimpanan dinamis. Mereka juga dapat dipanggil menggunakan sintaks panggilan fungsi biasa.

Hapus (dan baru) hanya bertanggung jawab untuk bagian 'manajemen memori'.

Jadi jelas dan diharapkan bahwa destructor hanya dipanggil sekali - untuk membersihkan instance objek. Apakah itu dipanggil dua kali, setiap destruktor harus memeriksa apakah sudah dipanggil.

Mario The Spoon
sumber
1
Operator hapus masih harus memanggil destruktor secara implisit, seperti yang dapat Anda lihat dari lognya sendiri yang menunjukkan destruktor setelah menghapus instance. Masalahnya di sini adalah bahwa override delete kelasnya memanggil :: delete yang mengarah ke override delete globalnya. Penghapusan penghapusan global itu hanya membebaskan memori sehingga tidak akan menginstal ulang destruktor.
Pickle Rick
Referensi menyatakan dengan jelas bahwa delete disebut SETELAH dekonstruksi objek
Mario The Spoon
Ya, global delete dipanggil setelah class delete. Ada dua penggantian di sini.
Pickle Rick
2
@ PickleRick - walaupun benar bahwa ekspresi delete harus memanggil destructor (dengan asumsi disediakan pointer ke tipe dengan destructor) atau satu set destruktor (form array), suatu operator delete()fungsi bukanlah hal yang sama dengan ekspresi delete. Destruktor dipanggil sebelum operator delete()fungsi dipanggil.
Peter
1
Ini akan membantu jika Anda menambahkan header ke qoute. Saat ini tidak jelas apa yang dimaksud. "Penyimpanan Dellocates ..." - siapa yang membatalkan alokasi penyimpanan?
idclev 463035818