Liệu xóa trên một con trỏ đến một lớp con gọi hàm hủy của lớp cơ sở?


165

Tôi có một class Asử dụng phân bổ bộ nhớ heap cho một trong các trường của nó. Lớp A được khởi tạo và được lưu trữ dưới dạng trường con trỏ trong một lớp khác ( class B.

Khi tôi thực hiện với một đối tượng của lớp B, tôi gọi delete, mà tôi giả sử gọi là hàm hủy ... Nhưng điều này có gọi cả hàm hủy của lớp A không?

Biên tập:

Từ các câu trả lời, tôi lấy đó (vui lòng chỉnh sửa nếu không chính xác):

  1. delete của một thể hiện của B gọi B :: ~ B ();
  2. mà gọi A::~A();
  3. A::~A nên rõ ràng deletetất cả các biến thành viên được phân bổ heap của đối tượng A;
  4. Cuối cùng, việc lưu trữ khối bộ nhớ đã nói của lớp B được trả về heap - khi mới được sử dụng, trước tiên, nó cấp phát một khối bộ nhớ trên heap, sau đó gọi các hàm tạo để khởi tạo nó, bây giờ sau khi tất cả các hàm hủy đã được gọi để hoàn thiện đối tượng khối nơi đối tượng cư trú được trả về heap.

Câu trả lời:


183

Hàm hủy của A sẽ chạy khi hết vòng đời. Nếu bạn muốn bộ nhớ của nó được giải phóng và bộ hủy chạy, bạn phải xóa nó nếu nó được phân bổ trên heap. Nếu nó được phân bổ trên ngăn xếp thì điều này sẽ tự động xảy ra (tức là khi nó vượt quá phạm vi; xem RAII). Nếu nó là thành viên của một lớp (không phải là con trỏ, mà là thành viên đầy đủ), thì điều này sẽ xảy ra khi đối tượng chứa bị phá hủy.

class A
{
    char *someHeapMemory;
public:
    A() : someHeapMemory(new char[1000]) {}
    ~A() { delete[] someHeapMemory; }
};

class B
{
    A* APtr;
public:
    B() : APtr(new A()) {}
    ~B() { delete APtr; }
};

class C
{
    A Amember;
public:
    C() : Amember() {}
    ~C() {} // A is freed / destructed automatically.
};

int main()
{
    B* BPtr = new B();
    delete BPtr; // Calls ~B() which calls ~A() 
    C *CPtr = new C();
    delete CPtr;
    B b;
    C c;
} // b and c are freed/destructed automatically

Trong ví dụ trên, mọi thao tác xóa và xóa [] là cần thiết. Và không cần xóa (hoặc thực sự có thể được sử dụng) khi tôi không sử dụng nó.

auto_ptr, unique_ptrshared_ptrvv ... là tuyệt vời cho việc quản lý đời này dễ dàng hơn nhiều:

class A
{
    shared_array<char> someHeapMemory;
public:
    A() : someHeapMemory(new char[1000]) {}
    ~A() { } // someHeapMemory is delete[]d automatically
};

class B
{
    shared_ptr<A> APtr;
public:
    B() : APtr(new A()) {}
    ~B() {  } // APtr is deleted automatically
};

int main()
{
    shared_ptr<B> BPtr = new B();
} // BPtr is deleted automatically

Tôi tự hỏi nếu destructor được gọi khi bạn giải phóng bộ nhớ chỉ một phần (ví dụ như sử dụng con trỏ sai.)
Tomáš Zato - Khôi phục Monica

Con trỏ chỉ là một con số. Bạn thậm chí có thể vô tình sử dụng ++toán tử trên nó. Vì vậy, tôi tự hỏi nếu con trỏ trỏ vào giữa dữ liệu lớp vẫn có hiệu lực.
Tomáš Zato - Phục hồi Monica

2
@ TomášZato: Nếu bạn gọi xóa trên một con trỏ ngẫu nhiên, thì bạn đã bị lừa. Không bao giờ có một lý do tốt để làm điều đó. Trên thực tế, nếu bạn gọi thủ công xóa bất cứ nơi nào khác ngoài công cụ hủy bỏ con trỏ thông minh, thì có lẽ bạn muốn xem xét lại lý do tại sao bạn không sử dụng con trỏ thông minh hoặc trình quản lý đối tượng khác.
Nhật thực

shared_array là từ boost only, vâng?
Dronz

30

Khi bạn gọi xóa trên một con trỏ được cấp phát bởi new, hàm hủy của đối tượng được trỏ sẽ được gọi.

A * p = new A;

delete p;    // A:~A() called for you on obkect pointed to by p

22

Nó được đặt tên là "hàm hủy", không phải "bộ giải mã".

Bên trong hàm hủy của mỗi lớp, bạn phải xóa tất cả các biến thành viên khác đã được cấp phát mới.

chỉnh sửa: Để làm rõ:

Nói rằng bạn có

struct A {}

class B {
    A *a;
public:
    B () : a (new A) {}
    ~B() { delete a; }
};

class C {
    A *a;
public:
    C () : a (new A) {}        
};

int main () {
    delete new B;
    delete new C;
}

Phân bổ một thể hiện của B và sau đó xóa là sạch, bởi vì những gì B phân bổ trong nội bộ cũng sẽ bị xóa trong hàm hủy.

Nhưng các thể hiện của lớp C sẽ rò rỉ bộ nhớ, vì nó cấp phát một thể hiện của A mà nó không giải phóng (trong trường hợp này C thậm chí không có hàm hủy).


5

Nếu bạn có một con trỏ thông thường ( A*) thì hàm hủy sẽ không được gọi (và Aví dụ bộ nhớ sẽ không được giải phóng) trừ khi bạn thực hiện deleterõ ràng trong hàm Bhủy. Nếu bạn muốn tự động phá hủy hãy nhìn vào con trỏ thông minh như thế nào auto_ptr.


4

Bạn nên tự xóa A trong hàm hủy của B.


4
class B
{
public:
    B()
    {
       p = new int[1024];  
    }
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
        //p will not be deleted EVER unless you do it manually.
    }
    int *p;
};


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

Khi bạn làm:

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

Hàm hủy sẽ chỉ được gọi nếu lớp cơ sở của bạn có từ khóa ảo.

Sau đó, nếu bạn không có hàm hủy ảo 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 ().

Không có thành viên nào của B hoặc D được phân bổ trên heap sẽ bị hủy bỏ trừ khi bạn xóa chúng một cách rõ ràng. Và xóa chúng cũng sẽ gọi hàm hủy của chúng.


1

Bạn có một cái gì đó như

class B
{
   A * a;
}
B * b = new B;
b->a = new A;

Nếu sau đó bạn gọi delete b;, không có gì xảy ra với a và bạn bị rò rỉ bộ nhớ. Cố gắng nhớ delete b->a;không phải là một giải pháp tốt, nhưng có một vài người khác.

B::~B() {delete a;}

Đây là một hàm hủy cho B sẽ xóa a. (Nếu a là 0, thì việc xóa đó không làm gì cả. Nếu a không phải là 0 nhưng không trỏ đến bộ nhớ từ mới, bạn sẽ bị heap heap.)

auto_ptr<A> a;
...
b->a.reset(new A);

Bằng cách này, bạn không có một con trỏ, mà thay vào đó là auto_ptr <> (shared_ptr <> cũng sẽ làm như vậy hoặc các con trỏ thông minh khác) và nó sẽ tự động bị xóa khi b.

Một trong những cách này hoạt động tốt, và tôi đã sử dụng cả hai.


1

Tôi đã tự hỏi tại sao kẻ hủy diệt của lớp tôi không được gọi. Lý do là tôi đã quên bao gồm định nghĩa về lớp đó (#include "class.h"). Tôi chỉ có một tuyên bố như "lớp A;" và trình biên dịch hài lòng với nó và để tôi gọi "xóa".


Tăng mức cảnh báo của trình biên dịch
Phil1970

0

Không, con trỏ sẽ bị xóa. Bạn nên gọi xóa trên A rõ ràng trong hàm hủy của B.


Tôi đang làm điều này, câu hỏi của tôi là kẻ hủy diệt được gọi là?
Nick Bolton


0

không, nó sẽ không gọi hàm hủy cho lớp A, bạn nên gọi nó một cách rõ ràng (như PoweRoy đã nói), xóa dòng 'xóa ptr;' ví dụ để so sánh ...

  #include <iostream>

  class A
  {
     public:
        A(){};
        ~A();
  };

  A::~A()
  {
     std::cout << "Destructor of A" << std::endl;
  }

  class B
  {
     public:
        B(){ptr = new A();};
        ~B();
     private:
        A* ptr;
  };

  B::~B()
  {
     delete ptr;
     std::cout << "Destructor of B" << std::endl;
  }

  int main()
  {
     B* b = new B();
     delete b;
     return 0;
  }
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.