Tôi có cần phải gọi một cách rõ ràng các hàm hủy ảo cơ sở không?


350

Khi ghi đè một lớp trong C ++ (với hàm hủy ảo) Tôi đang thực hiện lại hàm hủy như là ảo trên lớp kế thừa, nhưng tôi có cần gọi hàm hủy không?

Nếu vậy tôi tưởng tượng nó giống như thế này ...

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

    // Some destructing specific to MyChildClass
}

Tôi có đúng không

Câu trả lời:


469

Không, các tàu khu trục được gọi tự động theo thứ tự ngược lại của xây dựng. (Các lớp cơ sở cuối cùng). Đừng gọi các hàm hủy lớp cơ sở.


Những gì về hủy diệt ảo thuần túy? Trình liên kết của tôi đang cố gắng gọi nó ở phần cuối của hàm hủy không ảo của lớp kế thừa của tôi;
cjcurrie

40
bạn không thể có một kẻ hủy diệt ảo thuần túy mà không có cơ thể. Chỉ cần cho nó một cơ thể trống rỗng. Với một phương thức ảo thuần túy thông thường, hàm ghi đè được gọi thay vào đó, với các hàm hủy, tất cả chúng đều được gọi, vì vậy bạn phải cung cấp một cơ thể. = 0 chỉ có nghĩa là nó phải được ghi đè, do đó vẫn là một cấu trúc hữu ích nếu bạn cần nó.
Lou Franco

1
Câu hỏi này có thể liên quan và câu hỏi trợ giúp / 15265106 / ca-thiếu-vtable-error .
Paul-Sebastian Manole

Tại sao mã của Nick Bolton không gây ra lỗi phân đoạn mặc dù nó gọi hàm hủy cơ sở hai lần, trong khi gọi deletecon trỏ đến lớp cơ sở hai lần có gây ra lỗi phân đoạn không?
Maggyero

2
Bạn không được đảm bảo một lỗi phân đoạn với bất kỳ mã sai. Ngoài ra, gọi một hàm hủy không giải phóng bộ nhớ.
Lou Franco

92

Không, bạn không cần phải gọi hàm hủy cơ sở, hàm hủy cơ sở luôn được gọi bởi hàm hủy có nguồn gốc. Xin vui lòng xem câu trả lời liên quan của tôi ở đây để ra lệnh hủy diệt .

Để hiểu lý do tại sao bạn muốn một hàm hủy ảo trong lớp cơ sở, vui lòng xem mã bên dưới:

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


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

Khi bạn làm:

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

Sau đó, nếu bạn không có hàm hủy ảo trong B, chỉ ~ B () sẽ được gọi. Nhưng vì bạn có một hàm hủy ảo, đầu tiên ~ D () sẽ được gọi, sau đó ~ B ().


20
Vui lòng bao gồm đầu ra chương trình (giả). nó sẽ giúp người đọc
Kuldeep Singh Dhaka

@KuldeepSinghDhaka Người đọc có thể thấy nó trực tiếp tại Wandbox.org/permlink/KQtbZG1hjVgceSlO .
con lợn

27

Những gì người khác nói, nhưng cũng lưu ý rằng bạn không phải khai báo ảo hủy trong lớp dẫn xuất. Khi bạn khai báo một hàm hủy, như bạn làm trong lớp cơ sở, tất cả các hàm hủy có nguồn gốc sẽ là ảo cho dù bạn có khai báo chúng như vậy hay không. Nói cách khác:

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

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

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

1
Nếu ~ B không được khai báo ảo thì sao? Là ~ C vẫn ảo?
Sẽ

5
Đúng. Khi một phương thức ảo (bất kỳ, không chỉ là hàm hủy) được khai báo là ảo, tất cả các phần ghi đè của phương thức đó trong các lớp dẫn xuất đều tự động ảo. Trong trường hợp này, ngay cả khi bạn không khai báo ~ B ảo, nó vẫn vậy, và ~ C cũng vậy.
boycy

1
Nhưng không giống như các phương thức được ghi đè khác có cùng tên và tham số của các phương thức tương ứng của chúng trong lớp cơ sở, tên hàm hủy là khác nhau. @boycy
Yuan Wen

1
@YuanWen không nó sẽ không, hàm hủy có nguồn gốc (một và duy nhất) luôn ghi đè lên hàm hủy (một và duy nhất) của lớp cơ sở.
boycy

10

Không giống như các phương thức ảo khác, trong đó bạn sẽ gọi phương thức Base một cách rõ ràng từ cuộc gọi được tạo ra để 'chuỗi' cuộc gọi, trình biên dịch tạo mã để gọi các hàm hủy theo thứ tự ngược lại trong đó các hàm tạo của chúng được gọi.


9

Không, bạn không bao giờ gọi hàm hủy lớp cơ sở, nó luôn được gọi tự động như những người khác đã chỉ ra nhưng đây là bằng chứng về khái niệm với kết quả:

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;
}

Đầu ra là:

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 . . .

Nếu bạn đặt hàm hủy của lớp cơ sở là ảo, thì kết quả của trường hợp 3 sẽ giống như trường hợp 1 & 2.


Minh họa tốt. Nếu bạn cố gọi hàm hủy lớp cơ sở từ lớp dẫn xuất, bạn sẽ gặp lỗi trình biên dịch tương tự như "error: không có chức năng khớp nào để gọi đến 'BASE :: BASE ()' <newline> ~ BASE ();" Ít nhất đây là hành vi từ trình biên dịch g ++ 7.x của tôi.
Kemin Zhou


1

Các hàm hủy trong C ++ tự động được gọi theo thứ tự các cấu trúc của chúng (Xuất phát sau đó là Base) chỉ khi khai báo hủy lớp cơ sởvirtual .

Nếu không, thì chỉ có hàm hủy lớp cơ sở được gọi tại thời điểm xóa đối tượng.

Ví dụ: Không có Destructor ảo

#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;

}

Đầu ra

Base Constructor
Derived Constructor
Base Destructor

Ví dụ: Với cơ sở hủy diệt ảo cơ sở

#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;

}

Đầu ra

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

Nên khai báo hàm hủy lớp cơ sở virtualnếu không, nó gây ra hành vi không xác định.

Tham khảo: Máy hủy ảo

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.