ฉันต้องโทรหา destructor เสมือนจริงอย่างชัดเจนหรือไม่


351

เมื่อลบล้างคลาสใน C ++ (พร้อมด้วย destructor เสมือนจริง) ฉันกำลังใช้ destructor อีกครั้งเป็นเสมือนบนคลาสที่สืบทอด แต่ฉันต้องเรียกใช้ตัวทำลายฐานหรือไม่?

ถ้าอย่างนั้นฉันคิดว่ามันเป็นเช่นนี้ ...

MyChildClass::~MyChildClass() // virtual in header
{
    // Call to base destructor...
    this->MyBaseClass::~MyBaseClass();

    // Some destructing specific to MyChildClass
}

ฉันถูกไหม?

คำตอบ:


472

ไม่ destructors ถูกเรียกโดยอัตโนมัติในลำดับย้อนหลังของการก่อสร้าง (คลาสฐานสุดท้าย) อย่าเรียกตัวทำลายคลาสพื้นฐาน


สิ่งที่เกี่ยวกับ destructors เสมือนบริสุทธิ์? ตัวเชื่อมโยงของฉันพยายามเรียกมันในตอนท้ายของตัวทำลายแบบไม่เสมือนของคลาสที่สืบทอดมาของฉัน
cjcurrie

40
คุณไม่สามารถมี destructor เสมือนแท้โดยไม่มีเนื้อหา เพียงแค่ให้ร่างกายที่ว่างเปล่า ด้วยวิธีการเสมือนที่บริสุทธิ์ปกติฟังก์ชั่นการเอาชนะจะถูกเรียกแทนด้วย destructors พวกมันจะถูกเรียกใช้ดังนั้นคุณต้องจัดเตรียมเนื้อหา = 0 หมายถึงว่ามันจะต้องถูกแทนที่ดังนั้นยังคงเป็นโครงสร้างที่มีประโยชน์หากคุณต้องการ
Lou Franco

1
คำถามนี้อาจจะเกี่ยวข้องและช่วยเหลือคำถาม / 15265106 / CA-หายไป vtable ข้อผิดพลาด
Manole Paul-Sebastian

ทำไมรหัสของ Nick Bolton ไม่ทำให้เกิดความผิดพลาดในการแบ่งกลุ่มแม้ว่ามันจะเรียกตัวทำลายฐานสองครั้งในขณะที่การเรียกdeleteตัวชี้ไปยังคลาสพื้นฐานสองครั้งจะทำให้เกิดการแบ่งกลุ่มได้หรือไม่
Maggyero

2
คุณไม่รับประกันความผิดพลาดในการแบ่งส่วนด้วยรหัสที่ผิด นอกจากนี้การเรียก destructor จะไม่ปล่อยหน่วยความจำ
Lou Franco

92

ไม่ว่าคุณไม่จำเป็นต้องโทรหาผู้ทำลายล้างฐานผู้ทำลายฐานจะถูกเรียกร้องให้คุณเสมอโดยผู้ทำลายล้างที่ได้รับมา โปรดดูคำตอบที่เกี่ยวข้องของฉันที่นี่สำหรับคำสั่งของการทำลายโปรดดูคำตอบที่เกี่ยวข้องของฉันที่นี่สำหรับการสั่งซื้อของการทำลาย

เพื่อให้เข้าใจว่าทำไมคุณต้องการตัวทำลายเสมือนในคลาสฐานโปรดดูรหัสด้านล่าง:

class B
{
public:
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
    }
};


class D : public B
{
public:
    virtual ~D()
    {
        cout<<"D destructor"<<endl;
    }
};

เมื่อคุณทำ:

B *pD = new D();
delete pD;

ถ้าคุณไม่มีตัวทำลายเสมือนใน B จะมีเพียง ~ B () เท่านั้นที่จะถูกเรียก แต่เนื่องจากคุณมีตัวทำลายเสมือนระบบจะเรียกชื่อ ~ D () ก่อนแล้วจึง ~ B ()


20
โปรดรวมเอาท์พุทโปรแกรม (หลอก) มันจะช่วยให้ผู้อ่าน
Kuldeep Singh Dhaka

@KuldeepSinghDhaka ผู้อ่านสามารถดูได้อาศัยอยู่ที่wandbox.org/permlink/KQtbZG1hjVgceSlO
สุกร

27

สิ่งที่คนอื่นพูด แต่โปรดทราบว่าคุณไม่จำเป็นต้องประกาศตัวทำลายล้างในคลาสที่ได้รับ เมื่อคุณประกาศ destructor เสมือนเช่นเดียวกับที่คุณทำในชั้นฐาน destructors ที่ได้รับทั้งหมดจะเป็นเสมือนจริงไม่ว่าคุณจะประกาศพวกเขาเป็นอย่างนั้นหรือไม่ ในคำอื่น ๆ :

struct A {
   virtual ~A() {}
};

struct B : public A {
   virtual ~B() {}   // this is virtual
};

struct C : public A {
   ~C() {}          // this is virtual too
};

1
เกิดอะไรขึ้นถ้า ~ B ไม่ได้ประกาศให้เสมือน ~ C ยังคงเป็นเสมือนหรือไม่
จะ

5
ใช่. เมื่อมีการประกาศเมธอดเสมือน (ใด ๆ ไม่ใช่แค่ตัวทำลาย) เสมือนการแทนที่ทั้งหมดของเมธอดนั้นในคลาสที่ได้รับจะเสมือนโดยอัตโนมัติ ในกรณีนี้แม้ว่าคุณจะไม่ได้ประกาศ ~ B เสมือนจริงมันก็ยังเป็นเช่นนั้นและก็คือ ~ C
boycy

1
แต่แตกต่างจากวิธีการแทนที่อื่น ๆ ที่มีชื่อเดียวกันและพารามิเตอร์ของวิธีการที่สอดคล้องกันของพวกเขาในชั้นฐานชื่อ destructor จะแตกต่างกันมันจะมีความสำคัญ? @boycy
Yuan Wen

1
@ หยวนไม่ว่ามันจะไม่เกิดขึ้นตัวทำลายที่ได้รับหนึ่ง (เท่านั้น) จะแทนที่ตัวทำลายของคลาสพื้นฐาน (หนึ่งและเท่านั้น) เสมอ
boycy

10

ไม่ต่างจากวิธีเสมือนอื่น ๆ ที่คุณจะเรียกใช้เมธอด Base อย่างชัดเจนจาก Derived to 'chain' the call คอมไพเลอร์สร้างรหัสเพื่อเรียก destructors ตามลำดับย้อนกลับที่ถูกเรียก constructors


9

ไม่คุณไม่เคยเรียกตัวทำลายคลาสพื้นฐานมันจะถูกเรียกโดยอัตโนมัติเหมือนที่คนอื่น ๆ ได้ชี้ แต่นี่เป็นข้อพิสูจน์แนวคิดเกี่ยวกับผลลัพธ์:

class base {
public:
    base()  { cout << __FUNCTION__ << endl; }
    ~base() { cout << __FUNCTION__ << endl; }
};

class derived : public base {
public:
    derived() { cout << __FUNCTION__ << endl; }
    ~derived() { cout << __FUNCTION__ << endl; } // adding call to base::~base() here results in double call to base destructor
};


int main()
{
    cout << "case 1, declared as local variable on stack" << endl << endl;
    {
        derived d1;
    }

    cout << endl << endl;

    cout << "case 2, created using new, assigned to derive class" << endl << endl;
    derived * d2 = new derived;
    delete d2;

    cout << endl << endl;

    cout << "case 3, created with new, assigned to base class" << endl << endl;
    base * d3 = new derived;
    delete d3;

    cout << endl;

    return 0;
}

ผลลัพธ์คือ:

case 1, declared as local variable on stack

base::base
derived::derived
derived::~derived
base::~base


case 2, created using new, assigned to derive class

base::base
derived::derived
derived::~derived
base::~base


case 3, created with new, assigned to base class

base::base
derived::derived
base::~base

Press any key to continue . . .

หากคุณตั้งค่าตัวทำลายคลาสพื้นฐานให้เป็นเวอร์ชวลที่ควรใช้ผลลัพธ์ของเคส 3 จะเหมือนกับกรณีที่ 1 & 2


ภาพประกอบที่ดี ถ้าคุณพยายามเรียกตัวทำลายคลาสพื้นฐานจากคลาสที่ได้รับคุณควรได้รับข้อผิดพลาดคอมไพเลอร์คล้ายกับ "ข้อผิดพลาด: ไม่มีฟังก์ชันที่ตรงกันสำหรับการเรียกไปที่ 'BASE :: BASE ()' <newline> ~ BASE ();" อย่างน้อยนี่เป็นพฤติกรรมจากคอมไพเลอร์ g ++ 7.x ของฉัน
Kemin Zhou


1

Destructors ใน C ++ จะได้รับการเรียกโดยอัตโนมัติตามลำดับการสร้าง (Derived แล้ว Base) เฉพาะเมื่อมีการประกาศคลาส destructorvirtualระดับฐานมีการประกาศ

ถ้าไม่เช่นนั้นจะมีการเรียกใช้ตัวทำลายคลาสพื้นฐานเท่านั้นเมื่อทำการลบวัตถุ

ตัวอย่าง: ไม่มี Destructor เสมือน

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

เอาท์พุต

Base Constructor
Derived Constructor
Base Destructor

ตัวอย่าง: ด้วย Destructor เสมือนจริง

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  //virtual destructor
  virtual ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
    delete(n);  //deleting the memory used by pointer
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

เอาท์พุต

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

ขอแนะนำให้ประกาศตัวทำลายคลาสพื้นฐานเป็น virtualมิฉะนั้นจะทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด

การอ้างอิง: Virtual Destructor

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.