Lingkungan berkembang: GNU GCC (g ++) 4.1.2
Ketika saya mencoba untuk menyelidiki bagaimana meningkatkan 'cakupan kode - terutama cakupan fungsi' dalam pengujian unit, saya telah menemukan bahwa beberapa dtor kelas tampaknya dibuat beberapa kali. Apakah beberapa dari Anda tahu mengapa?
Saya mencoba dan mengamati apa yang saya sebutkan di atas dengan menggunakan kode berikut.
Dalam "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
Dalam "test.cpp"
#include <iostream>
#include "test.h"
BaseClass::~BaseClass()
{
std::cout << "BaseClass dtor invoked" << std::endl;
}
void BaseClass::someMethod()
{
std::cout << "Base class method" << std::endl;
}
DerivedClass::~DerivedClass()
{
std::cout << "DerivedClass dtor invoked" << std::endl;
}
void DerivedClass::someMethod()
{
std::cout << "Derived class method" << std::endl;
}
int main()
{
BaseClass* b_ptr = new BaseClass;
b_ptr->someMethod();
delete b_ptr;
}
Ketika saya membangun kode di atas (g ++ test.cpp -o test) dan kemudian melihat jenis simbol apa yang telah dihasilkan sebagai berikut,
nm --demangle test
Saya bisa melihat output berikut.
==== following is partial output ====
08048816 T DerivedClass::someMethod()
08048922 T DerivedClass::~DerivedClass()
080489aa T DerivedClass::~DerivedClass()
08048a32 T DerivedClass::~DerivedClass()
08048842 T BaseClass::someMethod()
0804886e T BaseClass::~BaseClass()
080488f6 T BaseClass::~BaseClass()
Pertanyaan saya adalah sebagai berikut.
1) Mengapa beberapa dtor telah dibuat (BaseClass - 2, DerivedClass - 3)?
2) Apa perbedaan di antara para dtor ini? Bagaimana beberapa dtor tersebut akan digunakan secara selektif?
Sekarang saya merasa bahwa untuk mencapai cakupan fungsi 100% untuk proyek C ++, kami perlu memahami ini sehingga saya dapat memanggil semua dtor tersebut dalam pengujian unit saya.
Saya akan sangat menghargai jika seseorang bisa memberi saya jawaban di atas.
sumber
Jawaban:
Pertama, tujuan dari fungsi ini dijelaskan dalam Itanium C ++ ABI ; lihat definisi di bawah "base object destructor", "complete object destructor", dan "deleting destructor". Pemetaan ke nama-nama yang rusak diberikan dalam 5.1.4.
Pada dasarnya:
operator delete
untuk benar-benar membebaskan memori.Jika Anda tidak memiliki kelas basis virtual, D2 dan D1 identik; GCC akan, pada tingkat pengoptimalan yang memadai, sebenarnya membuat alias simbol ke kode yang sama untuk keduanya.
sumber
struct B: virtual A
dan kemudianstruct C: B
, kemudian saat menghancurkanB
Anda memanggilB::D1
yang pada gilirannya memanggilA::D2
dan saat menghancurkanC
Anda memanggilC::D1
yang memanggilB::D2
danA::D2
(perhatikan bagaimanaB::D2
tidak memanggil A destruktor). Apa yang benar-benar menakjubkan dalam subdivisi ini adalah dapat mengelola semua situasi dengan hierarki linier sederhana dari 3 penghancur.Biasanya ada dua varian konstruktor ( not-in-charge / in-charge ) dan tiga destruktor ( not-in-charge / in-charge / in-charge menghapus ).
The tidak-in-charge ctor dan dtor digunakan ketika menangani sebuah objek dari sebuah kelas yang mewarisi dari kelas lain menggunakan
virtual
kata kunci, ketika objek bukan objek lengkap (sehingga objek saat ini "tidak bertanggung jawab" membangun atau Destructing objek basis virtual). Ctor ini menerima pointer ke objek basis virtual dan menyimpannya.The di-charge ctor dan dtors adalah untuk semua kasus lain, yaitu jika tidak ada virtual warisan yang terlibat; jika kelas memiliki penghancur virtual, penunjuk dtor penghapusan yang bertanggung jawab masuk ke slot vtable, sementara cakupan yang mengetahui tipe dinamis dari objek (yaitu untuk objek dengan durasi penyimpanan otomatis atau statis) akan menggunakan dtor yang bertanggung jawab (karena memori ini tidak boleh dibebaskan).
Contoh kode:
struct foo { foo(int); virtual ~foo(void); int bar; }; struct baz : virtual foo { baz(void); virtual ~baz(void); }; struct quux : baz { quux(void); virtual ~quux(void); }; foo::foo(int i) { bar = i; } foo::~foo(void) { return; } baz::baz(void) : foo(1) { return; } baz::~baz(void) { return; } quux::quux(void) : foo(2), baz() { return; } quux::~quux(void) { return; } baz b1; std::auto_ptr<foo> b2(new baz); quux q1; std::auto_ptr<foo> q2(new quux);
Hasil:
foo
,baz
danquux
menunjuk ke dtor yang bertanggung jawab menghapus masing-masing .b1
danb2
dibangun olehbaz()
in-charge , yang memanggilfoo(1)
-in-chargeq1
danq2
dibangun olehquux()
in-charge , yangfoo(2)
bertanggung jawab danbaz()
tidak-in-charge dengan penunjuk kefoo
objek yang dibangun sebelumnyaq2
dihancurkan oleh~auto_ptr()
in-charge , yang memanggil dtor virtual yang~quux()
bertanggung jawab menghapus , yang memanggil~baz()
not-in-charge ,~foo()
in-charge danoperator delete
.q1
dihancurkan oleh~quux()
in-charge , yang menyebut~baz()
not-in-charge dan~foo()
in-chargeb2
dihancurkan oleh~auto_ptr()
in-charge , yang memanggil dtor virtual yang~baz()
bertanggung jawab menghapus , yang memanggil~foo()
in-charge danoperator delete
b1
dihancurkan oleh~baz()
in-charge , yang memanggil~foo()
in-chargeSiapa pun yang berasal dari
quux
akan menggunakan yang tidak-in-charge ctor dan dtor dan mengambil tanggung jawab menciptakanfoo
objek.Pada prinsipnya, varian not-in-charge tidak pernah diperlukan untuk kelas yang tidak memiliki basis virtual; dalam hal ini, varian in-charge kadang-kadang disebut bersatu , dan / atau simbol untuk in-charge dan tidak-in-charge disebut juga sebagai implementasi tunggal.
sumber
delete
ekspresi baik sebagai bagian dari destruktor Anda sendiri, atau sebagai bagian dari panggilan destruktor sub-objek. Thedelete
ekspresi diimplementasikan baik sebagai panggilan melalui vtable objek jika memiliki destructor virtual (di mana kita menemukan di-charge menghapus , atau sebagai panggilan langsung ke objek yang di-charge destructor.delete
ekspresi tidak pernah menyebut tidak-in-charge varian, yang hanya digunakan oleh destructors lain ketika menghancurkan sebuah benda yang menggunakan virtual warisan.