Kapan menggunakan destruktor virtual?

1487

Saya memiliki pemahaman yang kuat tentang sebagian besar teori OO tetapi satu hal yang banyak membingungkan saya adalah penghancur virtual.

Saya pikir destruktor selalu dipanggil tidak peduli apa dan untuk setiap objek dalam rantai.

Kapan Anda dimaksudkan untuk membuatnya virtual dan mengapa?

Lodle
sumber
6
Lihat ini: Virtual Destructor
Naveen
146
Setiap destructor down dipanggil apa pun yang terjadi. virtualpastikan itu dimulai di atas bukan di tengah.
Mooing Duck
@ MoooDuck, itu komentar yang agak menyesatkan.
Euri Pinhollow
1
@ FranklinYu ada baiknya Anda bertanya karena sekarang saya tidak dapat melihat masalah dengan komentar itu (kecuali mencoba memberikan jawaban dalam komentar).
Euri Pinhollow

Jawaban:

1573

Destructor virtual berguna ketika Anda berpotensi menghapus instance kelas turunan melalui pointer ke kelas dasar:

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Di sini, Anda akan melihat bahwa saya tidak menyatakan destructor Base sebagai virtual. Sekarang, mari kita lihat cuplikan berikut:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Sejak destructor Basis adalah tidak virtualdan badalah Base*menunjuk ke Derivedobjek, delete bmemiliki perilaku tidak terdefinisi :

[Dalam delete b], jika tipe statis objek yang akan dihapus berbeda dari tipe dinamisnya, tipe statis akan menjadi kelas dasar dari tipe dinamis objek yang akan dihapus dan tipe statis harus memiliki destruktor virtual atau perilaku tidak terdefinisi .

Dalam sebagian besar implementasi, panggilan ke destruktor akan diselesaikan seperti kode non-virtual, yang berarti bahwa destruktor dari kelas dasar akan dipanggil tetapi bukan kelas turunan, yang mengakibatkan kebocoran sumber daya.

Singkatnya, selalu membuat destruktor kelas dasar virtualketika mereka dimaksudkan untuk dimanipulasi secara polimorfis.

Jika Anda ingin mencegah penghapusan instance melalui pointer kelas dasar, Anda bisa membuat destruktor kelas dasar dilindungi dan nonvirtual; dengan melakukannya, kompiler tidak akan membiarkan Anda memanggil deletepointer kelas dasar.

Anda dapat mempelajari lebih lanjut tentang virtualitas dan penghancur kelas dasar virtual dalam artikel ini dari Herb Sutter .

Luc Touraille
sumber
174
Ini akan menjelaskan mengapa saya mengalami kebocoran besar-besaran menggunakan pabrik yang saya buat sebelumnya. Semua masuk akal sekarang. Terima kasih
Lodle
8
Yah, ini adalah contoh buruk karena tidak ada anggota data. Bagaimana jika Basedan Derivedmemiliki semua variabel penyimpanan otomatis? yaitu tidak ada kode khusus "khusus" atau tambahan untuk dieksekusi di destruktor. Apakah tidak apa-apa untuk berhenti menulis perusak sama sekali? Atau akankah kelas turunannya masih mengalami kebocoran memori?
bobobobo
28
Dari artikel Herb Sutter: "Pedoman # 4: Penghancur kelas dasar harus bersifat publik dan virtual, atau dilindungi dan nonvirtual."
Sundae
3
Juga dari artikel - 'jika Anda menghapus secara polimorfik tanpa destruktor virtual, Anda memanggil momok menakutkan "perilaku tidak terdefinisi," momok yang secara pribadi saya lebih suka tidak temui di gang yang cukup terang sekalipun, terima kasih banyak.' lol
Bondolin
219

Konstruktor virtual tidak dimungkinkan tetapi destruktor virtual dimungkinkan. Mari kita bereksperimen .......

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Kode di atas menghasilkan yang berikut:

Base Constructor Called
Derived constructor called
Base Destructor called

Konstruksi objek turunan mengikuti aturan konstruksi tetapi ketika kita menghapus pointer "b" (basis pointer) kita telah menemukan bahwa hanya destruktor basis yang dipanggil. Tetapi ini tidak harus terjadi. Untuk melakukan hal yang sesuai, kita harus membuat basis destructor virtual. Sekarang mari kita lihat apa yang terjadi di bawah ini:

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Output berubah sebagai berikut:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

Jadi penghancuran penunjuk basis (yang mengambil alokasi pada objek turunan!) Mengikuti aturan penghancuran, yaitu pertama Derived, lalu Base. Di sisi lain, tidak ada yang seperti konstruktor virtual.

Tunvir Rahman Tusher
sumber
1
"konstruktor virtual tidak memungkinkan" berarti Anda tidak perlu menulis konstruktor virtual sendiri. Konstruksi objek yang diturunkan harus mengikuti rantai konstruksi dari yang diturunkan ke pangkalan. Jadi Anda tidak perlu menulis kata kunci virtual untuk konstruktor Anda. Terima kasih
Tunvir Rahman Tusher
4
@Murkantilism, "konstruktor virtual tidak dapat dilakukan" memang benar. Konstruktor tidak dapat ditandai virtual.
cmeub
1
@ cmeub, Tapi ada idiom untuk mencapai apa yang Anda inginkan dari konstruktor virtual. Lihat parashift.com/c++-faq-lite/virtual-ctors.html
cape1232
@TunvirRahmanTusher dapatkah Anda menjelaskan mengapa Base Destructor disebut ??
rimalonfire
@rimiro Otomatis oleh c ++. Anda dapat mengikuti tautan stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher
195

Menyatakan destruktor virtual di kelas dasar polimorfik. Ini adalah Item 7 di C ++ Efektif Scott Meyers ' . Meyers terus meringkas bahwa jika kelas memiliki setiap fungsi virtual, harus memiliki destructor virtual, dan bahwa kelas tidak dirancang untuk menjadi kelas dasar atau tidak dirancang untuk digunakan polymorphically harus tidak menyatakan destructors virtual.

Bill the Lizard
sumber
14
+ "Jika sebuah kelas memiliki fungsi virtual, ia harus memiliki destruktor virtual, dan bahwa kelas yang tidak dirancang untuk menjadi kelas dasar atau tidak dirancang untuk digunakan secara polimorfik tidak boleh menyatakan destruktor virtual.": Apakah ada kasus di mana masuk akal untuk melanggar aturan ini? Jika tidak, apakah masuk akal untuk meminta kompiler memeriksa kondisi ini dan mengeluarkan kesalahan apakah tidak puas?
Giorgio
@Iorgio Saya tidak tahu ada pengecualian dari aturan. Tapi saya tidak akan menilai diri saya sebagai pakar C ++, jadi Anda mungkin ingin memposting ini sebagai pertanyaan terpisah. Peringatan kompiler (atau peringatan dari alat analisis statis) masuk akal bagi saya.
Bill the Lizard
10
Kelas dapat dirancang untuk tidak dihapus melalui pointer tipe tertentu, namun masih memiliki fungsi virtual - contoh khasnya adalah antarmuka panggilan balik. Seseorang tidak menghapus implementasinya melalui penunjuk antarmuka panggilan balik karena itu hanya untuk berlangganan, tetapi memiliki fungsi virtual.
dascandy
3
@dascandy Persis - bahwa atau semua banyak situasi lain di mana kita menggunakan perilaku polimorfik tetapi tidak melakukan manajemen penyimpanan melalui pointer - mis. mempertahankan objek durasi otomatis atau statis, dengan pointer hanya digunakan sebagai rute pengamatan. Tidak perlu / tujuan dalam mengimplementasikan destructor virtual dalam kasus seperti itu. Karena kami hanya mengutip orang di sini, saya lebih suka Sutter dari atas: "Pedoman # 4: Penghancur kelas dasar harus publik dan virtual, atau dilindungi dan nonvirtual." Yang terakhir memastikan siapa pun yang secara tidak sengaja mencoba untuk menghapus melalui pointer basis ditunjukkan kesalahan dari cara mereka
underscore_d
1
@Iorgio Sebenarnya ada trik yang dapat digunakan dan menghindari panggilan virtual ke destruktor: mengikat melalui referensi const objek turunan ke pangkalan, seperti const Base& = make_Derived();. Dalam hal ini, destruktor dari Derivedprvalue akan dipanggil, bahkan jika itu bukan virtual, jadi orang menyimpan overhead yang diperkenalkan oleh vtables / vpointers. Tentu saja cakupannya sangat terbatas. Andrei Alexandrescu menyebutkan ini dalam bukunya Modern C ++ Design .
vsoftco
46

Perlu diketahui juga bahwa menghapus pointer kelas dasar ketika tidak ada destruktor virtual akan menghasilkan perilaku yang tidak ditentukan . Sesuatu yang saya pelajari baru-baru ini:

Bagaimana seharusnya menghapus utama di C ++ berperilaku?

Saya telah menggunakan C ++ selama bertahun-tahun dan saya masih bisa menggantung diri.

BigSandwich
sumber
Saya telah melihat pertanyaan Anda dan melihat bahwa Anda telah mendeklarasikan base destructor sebagai virtual. Jadi apakah "menghapus pointer kelas dasar ketika tidak ada destruktor virtual akan menghasilkan perilaku yang tidak terdefinisi" tetap valid sehubungan dengan pertanyaan Anda itu? Karena, dalam pertanyaan itu, ketika Anda memanggil hapus, kelas turunan (dibuat oleh operator barunya) diperiksa untuk versi yang kompatibel terlebih dahulu. Karena ditemukan di sana, itu disebut. Jadi, tidakkah Anda berpikir akan lebih baik untuk mengatakan "menghapus pointer kelas dasar ketika tidak ada destruktor akan menghasilkan perilaku yang tidak terdefinisi"?
ubuntugod
Itu hal yang hampir sama. Konstruktor default bukan virtual.
BigSandwich
41

Jadikan destruktor virtual kapan pun kelas Anda polimorfik.

yesraaj
sumber
13

Memanggil destruktor melalui pointer ke kelas dasar

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

Panggilan destruktor virtual tidak berbeda dari panggilan fungsi virtual lainnya.

Sebab base->f(), panggilan akan dikirim ke Derived::f(), dan itu sama untuk base->~Base()- fungsi utamanya - yang Derived::~Derived()akan dipanggil.

Hal yang sama terjadi ketika destruktor dipanggil secara tidak langsung, mis delete base;. The deletepernyataan akan memanggil base->~Base()yang akan diberangkatkan ke Derived::~Derived().

Kelas abstrak dengan destruktor non-virtual

Jika Anda tidak akan menghapus objek melalui pointer ke kelas dasarnya - maka tidak perlu memiliki destruktor virtual. Buat saja protectedsupaya itu tidak akan dipanggil secara tidak sengaja:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}
Abyx
sumber
Apakah perlu untuk mendeklarasikan secara eksplisit ~Derived()di semua kelas turunan, meskipun itu adil ~Derived() = default? Atau apakah itu tersirat oleh bahasa (membuatnya aman untuk dihilangkan)?
Ponkadoodle
@ Wallalloloo tidak, hanya menyatakannya saat diperlukan. Misalnya untuk dimasukkan ke protectedbagian, atau untuk memastikan bahwa itu virtual dengan menggunakan override.
Abyx
9

Saya suka berpikir tentang antarmuka dan implementasi antarmuka. Dalam antarmuka C ++ berbicara adalah kelas virtual murni. Destructor adalah bagian dari antarmuka dan diharapkan untuk diimplementasikan. Oleh karena itu destructor harus berupa virtual murni. Bagaimana dengan konstruktor? Konstruktor sebenarnya bukan bagian dari antarmuka karena objek selalu dipakai secara eksplisit.

Dragan Ostojic
sumber
2
Itu adalah perspektif yang berbeda pada pertanyaan yang sama. Jika kita berpikir dalam hal antarmuka bukan kelas dasar vs kelas turunan maka itu kesimpulan alami: jika itu adalah bagian dari antarmuka daripada menjadikannya virtual. Jika tidak, jangan.
Dragan Ostojic
2
+1 untuk menyatakan kesamaan konsep antarmuka OO dan kelas virtual murni C ++ . Mengenai destructor diharapkan untuk diimplementasikan : yang seringkali tidak perlu. Kecuali sebuah kelas mengelola sumber daya seperti memori mentah yang dialokasikan secara dinamis (misalnya, bukan melalui penunjuk cerdas), pegangan file atau pegangan basis data, menggunakan destruktor default yang dibuat oleh kompiler baik-baik saja di kelas turunan. Dan perhatikan bahwa jika destruktor (atau fungsi apa pun) dideklarasikan virtualdi kelas dasar, secara otomatis virtualdi kelas turunan, bahkan jika tidak dinyatakan demikian.
DavidRR
Ini melewatkan detail penting bahwa destructor belum tentu merupakan bagian dari antarmuka. Seseorang dapat dengan mudah memprogram kelas yang memiliki fungsi polimorfik tetapi yang tidak dikelola / penelepon tidak dapat dihapus. Maka destructor virtual tidak memiliki tujuan. Tentu saja, untuk memastikan ini, destruktor non-virtual - mungkin default - harus non-publik. Jika saya harus menebak, saya akan mengatakan kelas seperti itu lebih sering digunakan secara internal untuk proyek, tetapi itu tidak membuat mereka kurang relevan sebagai contoh / nuansa dalam semua ini.
underscore_d
8

Kata kunci virtual untuk destruktor diperlukan ketika Anda ingin destruktor yang berbeda harus mengikuti urutan yang tepat ketika objek sedang dihapus melalui pointer kelas dasar. sebagai contoh:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

Jika destruktor kelas dasar Anda adalah virtual maka objek akan dihancurkan dalam urutan (objek pertama yang diturunkan kemudian basis). Jika destruktor kelas dasar Anda TIDAK virtual maka hanya objek kelas dasar yang akan dihapus (karena pointer adalah kelas dasar "Base * myObj"). Jadi akan ada kebocoran memori untuk objek yang diturunkan.

Mukul Kashmira
sumber
7

Sederhananya, Virtual destructor adalah untuk menghancurkan sumber daya dalam urutan yang tepat, ketika Anda menghapus pointer kelas dasar yang menunjuk ke objek kelas turunan.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak

GiBB Prakash
sumber
Tidak memiliki destruktor virtual basis dan memanggil deletebasis pointer mengarah ke perilaku yang tidak ditentukan.
James Adkison
@ JamesAdkison mengapa itu mengarah pada perilaku yang tidak terdefinisi ??
rimalonfire
@rimiro Itulah yang dikatakan standar . Saya tidak memiliki salinan tetapi tautan itu membawa Anda ke komentar di mana seseorang mereferensikan lokasi dalam standar.
James Adkison
@rimiro "Jika penghapusan, oleh karena itu, dapat dilakukan secara polimorfik melalui antarmuka kelas dasar, maka itu harus berperilaku secara virtual dan harus virtual. Memang, bahasa mengharuskannya - jika Anda menghapus secara polimorfik tanpa destruktor virtual, Anda memanggil momok yang ditakuti dari "Perilaku yang tidak terdefinisi," momok yang secara pribadi lebih baik aku tidak temui di lorong yang cukup terang, terima kasih banyak. " ( Gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison
4

Penghancur kelas dasar virtual adalah "praktik terbaik" - Anda harus selalu menggunakannya untuk menghindari kebocoran memori (sulit dideteksi). Dengan menggunakan mereka, Anda dapat yakin bahwa semua destruktor dalam rantai warisan kelas Anda sedang dipanggil (dalam urutan yang benar). Mewarisi dari kelas dasar menggunakan penghancur virtual membuat destruktor dari kelas warisan secara otomatis juga virtual (sehingga Anda tidak perlu mengetik ulang 'virtual' dalam deklarasi destruktor kelas warisan).

Pelayan
sumber
4

Jika Anda menggunakan shared_ptr(hanya shared_ptr, bukan unique_ptr), Anda tidak harus memiliki virtual destructor kelas dasar:

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

keluaran:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Zhenxiao Hao
sumber
Meskipun ini mungkin, saya akan mencegah siapa pun untuk menggunakan ini. Overhead dari destructor virtual sangat kecil dan ini hanya memungkinkan untuk dikacaukan, terutama oleh programmer yang kurang berpengalaman, yang tidak mengetahui hal ini. virtualKata kunci kecil itu bisa menyelamatkan Anda dari banyak penderitaan.
Michal Štein
3

Apa itu destruktor virtual atau cara menggunakan destruktor virtual

Kelas destruktor adalah fungsi dengan nama yang sama dari kelas sebelumnya dengan ~ yang akan mengalokasikan kembali memori yang dialokasikan oleh kelas. Mengapa kita membutuhkan destruktor virtual

Lihat contoh berikut dengan beberapa fungsi virtual

Sampel juga memberi tahu bagaimana Anda dapat mengubah huruf ke atas atau bawah

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

Dari sampel di atas Anda dapat melihat bahwa destruktor untuk kelas MakeUpper dan MakeLower tidak dipanggil.

Lihat contoh selanjutnya dengan destruktor virtual

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

Destroyer virtual akan memanggil secara eksplisit run time destructor kelas yang paling diturunkan sehingga akan dapat menghapus objek dengan cara yang tepat.

Atau kunjungi tautannya

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138

pengguna2578542
sumber
2

ketika Anda perlu memanggil destruktor kelas turunan dari kelas dasar. Anda perlu mendeklarasikan destruktor kelas dasar virtual di kelas dasar.

pengguna2641018
sumber
2

Saya pikir inti dari pertanyaan ini adalah tentang metode virtual dan polimorfisme, bukan destruktor secara khusus. Ini adalah contoh yang lebih jelas:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Akan dicetak:

This is B.

Tanpa virtualitu akan mencetak:

This is A.

Dan sekarang Anda harus memahami kapan harus menggunakan destruktor virtual.

Gonjay
sumber
Tidak, ini hanya vulkanisir dasar-dasar fungsi virtual, mengabaikan nuansa kapan / mengapa destruktor harus menjadi satu - yang tidak seintuitif, oleh karena itu mengapa OP mengajukan pertanyaan. (Juga, mengapa alokasi dinamis yang tidak perlu di sini? Lakukan saja B b{}; A& a{b}; a.foo();. Memeriksa NULL- yang seharusnya nullptr- sebelum delete- dengan indendasi yang salah - tidak diperlukan: delete nullptr;didefinisikan sebagai no-op. Jika ada, Anda harus memeriksa ini sebelum memanggil ->foo(), sebagai perilaku dinyatakan tidak terdefinisi dapat terjadi jika newentah bagaimana gagal.)
underscore_d
2
Hal ini aman untuk memanggil deletepada NULLpointer (yaitu, Anda tidak perlu if (a != NULL)penjaga).
James Adkison
@ Safesh Ya, saya tahu. Itulah yang saya katakan dalam komentar saya
James Adkison
1

Saya pikir akan bermanfaat untuk membahas perilaku "tidak terdefinisi", atau setidaknya perilaku tidak terdefinisi "crash" yang mungkin terjadi ketika menghapus kelas dasar (/ struct) tanpa destruktor virtual, atau lebih tepatnya tidak ada vtable. Kode di bawah daftar beberapa struct sederhana (hal yang sama berlaku untuk kelas).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Saya tidak menyarankan apakah Anda memerlukan destruktor virtual atau tidak, meskipun saya pikir secara umum itu adalah praktik yang baik untuk memilikinya. Saya hanya menunjukkan alasan Anda mungkin berakhir dengan crash jika kelas dasar Anda (/ struct) tidak memiliki vtable dan kelas turunan Anda (/ struct) tidak dan Anda menghapus objek melalui kelas dasar (/ struct) penunjuk. Dalam hal ini, alamat yang Anda berikan ke rutin bebas heap tidak valid dan dengan demikian alasan kecelakaan itu.

Jika Anda menjalankan kode di atas Anda akan melihat dengan jelas ketika masalah terjadi. Ketika pointer ini dari kelas dasar (/ struct) berbeda dari pointer ini dari kelas turunan (/ struct) Anda akan mengalami masalah ini. Dalam contoh di atas, struct a dan b tidak memiliki vtables. structs c dan d memang memiliki vtables. Dengan demikian pointer a atau b ke instance objek ac atau d akan diperbaiki hingga memperhitungkan vtable. Jika Anda melewatkan a atau b pointer ini untuk menghapusnya akan macet karena alamat tidak valid untuk rutinitas heap gratis.

Jika Anda berencana untuk menghapus turunan instance yang memiliki vtable dari pointer kelas dasar, Anda perlu memastikan kelas dasar memiliki vtable. Salah satu cara untuk melakukannya adalah dengan menambahkan destruktor virtual, yang Anda mungkin ingin membersihkan sumber daya dengan benar.

nickdu
sumber
0

Definisi dasar tentang virtualini menentukan apakah fungsi anggota suatu kelas dapat dikuasai secara berlebihan dalam kelas turunannya.

D-tor kelas pada dasarnya disebut di akhir ruang lingkup, tetapi ada masalah, misalnya ketika kita mendefinisikan instance pada Heap (alokasi dinamis), kita harus menghapusnya secara manual.

Segera setelah instruksi dieksekusi, destructor kelas dasar dipanggil, tetapi tidak untuk yang diturunkan.

Contoh praktis adalah ketika, di bidang kontrol, Anda harus memanipulasi efektor, aktuator.

Pada akhir ruang lingkup, jika penghancur salah satu elemen daya (Aktuator), tidak dipanggil, akan ada konsekuensi fatal.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}
rekkalmd
sumber
-1

Setiap kelas yang diwariskan kepada publik, polimorfik atau tidak, harus memiliki destruktor virtual. Dengan kata lain, jika dapat ditunjukkan oleh pointer kelas dasar, kelas dasarnya harus memiliki destruktor virtual.

Jika virtual, destruktor kelas turunan dipanggil, maka konstruktor kelas dasar. Jika tidak virtual, hanya destructor kelas dasar yang dipanggil.

Syed H
sumber
Saya akan mengatakan ini hanya perlu "jika itu dapat ditunjuk oleh pointer kelas dasar" dan dapat dihapus secara publik. Tapi saya kira tidak ada salahnya untuk membiasakan menambahkan dtor virtual jika mereka mungkin diperlukan nanti.
underscore_d