Tại sao hàm hủy không được gọi trong toán tử xóa?


16

Tôi đã cố gắng để gọi ::deletecho một lớp học trong operator deleteđó. Nhưng kẻ hủy diệt không được gọi.

Tôi định nghĩa một lớp MyClassoperator deleteđã bị quá tải. Toàn cầu operator deletecũng quá tải. Các quá tải operator deletecủa MyClasssẽ gọi toàn cầu quá tải operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

Đầu ra là:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Thực tế:

Chỉ có một cuộc gọi đến destructor trước khi gọi quá tải operator deletecủa MyClass.

Hy vọng:

Có hai cuộc gọi đến hàm hủy. Một trước khi gọi quá tải operator deletecủa MyClass. Khác trước khi gọi toàn cầu operator delete.


6
MyClass::operator new()nên phân bổ bộ nhớ thô, của (ít nhất) sizebyte. Nó không nên cố gắng xây dựng hoàn toàn một thể hiện của MyClass. Các constructor của MyClassđược thực hiện sau MyClass::operator new(). Sau đó, deletebiểu thức trong main()gọi hàm hủy và giải phóng bộ nhớ (không gọi lại hàm hủy). Các ::delete pbiểu hiện không có thông tin về loại đối tượng pđiểm vào, vì plà một void *, vì vậy không thể gọi destructor.
Peter


2
Các câu trả lời đã được cung cấp cho bạn là chính xác, nhưng tôi tự hỏi: tại sao bạn lại cố gắng ghi đè mới và xóa? Trường hợp sử dụng thông thường là triển khai quản lý bộ nhớ tùy chỉnh (GC, bộ nhớ không đến từ malloc mặc định (), v.v.). Có thể bạn đang sử dụng sai công cụ cho những gì bạn đang cố gắng đạt được.
noamtm

2
::delete p;gây ra hành vi không xác định do loại *pkhông giống với loại đối tượng bị xóa (cũng không phải là lớp cơ sở với hàm hủy ảo)
MM

@MM Các trình biên dịch chính chỉ cảnh báo tối đa về nó, vì vậy tôi đã không nhận ra rằng void*toán hạng thậm chí còn không rõ ràng. [expr.delete] / 1 : " Toán hạng sẽ là con trỏ tới loại đối tượng hoặc loại lớp. [...] Điều này ngụ ý rằng một đối tượng không thể bị xóa bằng cách sử dụng một con trỏ có kiểu void vì void không phải là một loại đối tượng. * "@OP Tôi đã sửa đổi câu trả lời của mình.
quả óc chó

Câu trả lời:


17

Bạn đang lạm dụng operator newoperator delete. Các toán tử này là các hàm phân bổ và giải quyết. Họ không chịu trách nhiệm xây dựng hoặc phá hủy các đối tượng. Họ chỉ chịu trách nhiệm cung cấp bộ nhớ trong đó đối tượng sẽ được đặt.

Các phiên bản toàn cầu của các chức năng này là ::operator new::operator delete. ::new::deletelà các biểu thức mới / xóa, như new/ delete, khác với các biểu thức, trong đó ::new::deletesẽ bỏ qua các lớp operator new/ operator deletequá tải cụ thể của lớp .

Cấu trúc / xóa biểu thức mới / hủy bỏ phân bổ / giải quyết (bằng cách gọi phần thích hợp operator newhoặc operator deletetrước khi xây dựng hoặc sau khi hủy).

Vì quá tải của bạn chỉ chịu trách nhiệm cho phần phân bổ / giải quyết, nên nó sẽ gọi ::operator new::operator deletethay vì ::new::delete.

Các deletetrong delete myClass;có trách nhiệm gọi các hàm hủy.

::delete p;không gọi hàm hủy vì pcó kiểu void*và do đó biểu thức không thể biết hàm hủy nào sẽ gọi. Nó có thể sẽ gọi thay thế của bạn ::operator deleteđể giải phóng bộ nhớ, mặc dù sử dụng void*toán hạng dưới dạng biểu thức xóa là không đúng định dạng (xem chỉnh sửa bên dưới).

::new MyClass();gọi thay thế của bạn ::operator newđể phân bổ bộ nhớ và xây dựng một đối tượng trong đó. Con trỏ tới đối tượng này được trả về như void*biểu thức mới MyClass* myClass = new MyClass();, sau đó sẽ xây dựng một đối tượng khác trong bộ nhớ này, kết thúc vòng đời của đối tượng trước mà không gọi hàm hủy của nó.


Biên tập:

Nhờ nhận xét của @ MM về câu hỏi, tôi nhận ra rằng một void*toán hạng ::deletelà thực sự không đúng. ( [expr.delete] / 1 ) Tuy nhiên, các trình biên dịch chính dường như đã quyết định chỉ cảnh báo về điều này, không phải lỗi. Trước khi nó được định dạng sai, sử dụng ::deletetrên một void*hành vi đã không xác định, xem câu hỏi này .

Do đó, chương trình của bạn không được định dạng đúng và bạn không có bất kỳ đảm bảo nào rằng mã thực sự làm những gì tôi đã mô tả ở trên nếu nó vẫn được biên dịch.


Như @SanderDeDycker đã chỉ ra bên dưới câu trả lời của anh ta, bạn cũng có hành vi không xác định bởi vì bằng cách xây dựng một đối tượng khác trong bộ nhớ đã chứa MyClassđối tượng mà không gọi hàm hủy của đối tượng đó trước tiên, bạn đang vi phạm [basic.life] / 5 , điều đó cấm làm như vậy nếu chương trình phụ thuộc vào tác dụng phụ của kẻ hủy diệt. Trong trường hợp này, printfcâu lệnh trong hàm hủy có tác dụng phụ như vậy.


Việc sử dụng sai nhằm mục đích kiểm tra cách thức hoạt động của các nhà khai thác này. Tuy nhiên, cảm ơn câu trả lời của bạn. Có vẻ như câu trả lời duy nhất giải quyết vấn đề của tôi.
hết hạn

13

Quá tải cụ thể lớp học của bạn được thực hiện không chính xác. Điều này có thể được nhìn thấy trong đầu ra của bạn: hàm tạo được gọi hai lần!

Trong lớp cụ thể operator new, hãy gọi trực tiếp cho nhà điều hành toàn cầu:

return ::operator new(size);

Tương tự, trong lớp cụ thể operator delete, làm:

::operator delete(p);

Tham operator newkhảo trang tham khảo để biết thêm chi tiết.


Tôi biết hàm tạo được gọi hai lần bằng cách gọi :: new trong toán tử new và ý tôi là nó. Câu hỏi của tôi là tại sao hàm hủy không được gọi khi gọi :: xóa trong toán tử xóa?
hết hạn

1
@Exinc: Cố ý gọi hàm tạo lần thứ hai mà không gọi hàm hủy trước là một ý tưởng thực sự tồi. Đối với các hàm hủy không tầm thường (như của bạn), bạn thậm chí còn mạo hiểm vào lãnh thổ hành vi không xác định (nếu bạn phụ thuộc vào tác dụng phụ của kẻ hủy diệt, do bạn làm) - ref. [cơ bản. cuộc sống] §5 . Đừng làm điều này.
Sander De Dycker

1

Xem tài liệu tham khảo CPP :

operator delete, operator delete[]

Lưu trữ thỏa thuận trước đây được phân bổ bởi một kết hợp operator new. Các hàm phân bổ này được gọi bằng các biểu thức xóa và bởi các biểu thức mới để phân bổ bộ nhớ sau khi hủy (hoặc không xây dựng) các đối tượng có thời lượng lưu trữ động. Chúng cũng có thể được gọi bằng cú pháp gọi hàm thông thường.

Xóa (và mới) chỉ chịu trách nhiệm cho phần 'quản lý bộ nhớ'.

Vì vậy, rõ ràng và dự kiến ​​rằng hàm hủy chỉ được gọi một lần - để dọn sạch thể hiện của đối tượng. Nó sẽ được gọi hai lần, mọi hàm hủy sẽ phải kiểm tra xem nó đã được gọi chưa.


1
Toán tử xóa vẫn nên ngầm gọi hàm hủy, như bạn có thể thấy từ nhật ký của chính nó hiển thị hàm hủy sau khi xóa thể hiện. Vấn đề ở đây là ghi đè xóa lớp của anh ta đang gọi :: xóa dẫn đến ghi đè xóa toàn cục của anh ta. Việc ghi đè xóa toàn cục đó chỉ giải phóng bộ nhớ để nó không khôi phục được hàm hủy.
Pickle Rick

Các tham chiếu nêu rõ rằng xóa được gọi là giải cấu trúc đối tượng SAU
Mario Chiếc thìa

Có, xóa toàn cục được gọi sau khi xóa lớp. Có hai phần ghi đè ở đây.
Pickle Rick

2
@PickleRick - mặc dù đúng là một biểu thức xóa nên gọi hàm hủy (giả sử đã cung cấp một con trỏ tới một kiểu có hàm hủy) hoặc một tập hợp các hàm hủy (dạng mảng), một operator delete()hàm không giống như biểu thức xóa. Hàm hủy được gọi trước khi operator delete()hàm được gọi.
Peter

1
Nó sẽ giúp nếu bạn thêm một tiêu đề vào qoute. Hiện tại nó không rõ ràng những gì nó đề cập đến. "Dellocates lưu trữ ..." - ai giải quyết lưu trữ?
idclev 463035818
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.