2011-12-22 37 views
14

Câu hỏi này khác với 'Khi nào/tại sao tôi nên sử dụng hàm hủy là virtual? '.Hành vi phá hủy ảo và hành vi không xác định

struct B { 
    virtual void foo(); 
    ~B() {} // <--- not virtual 
}; 
struct D : B { 
    virtual void foo(); 
    ~D() {} 
}; 
B *p = new D; 
delete p; // D::~D() is not called 

Câu hỏi:

  1. này có thể được phân loại như là một hành vi không xác định (chúng tôi nhận thức được rằng ~D() sẽ không được gọi là chắc chắn)?
  2. Điều gì xảy ra nếu ~D() bị trống. Nó có ảnh hưởng đến mã theo bất kỳ cách nào không?
  3. Khi sử dụng new[]/delete[] với B* p;, các ~D() chắc chắn sẽ không được gọi, bất kể virtual Ness của destructor. Có phải là hành vi không xác định hoặc hành vi được xác định rõ ràng không?
+1

Tôi thường nghĩ đến việc yêu cầu điều tương tự. Tôi muốn một câu trả lời toàn diện xem xét ba trường hợp: (1) B không có phương pháp ảo, (2) B có một phương pháp ảo, nhưng một destructor không ảo, (3). B có một destructor ảo. Rõ ràng, chỉ sau này được xác định rõ: http://stackoverflow.com/questions/2065938/virtual-destructor –

+0

Đây là răng của tôi :) – mlvljr

Trả lời

19

khi nào/tại sao tôi nên sử dụng trình phá hủy ảo?
Thực hiện theo Herb Sutters guideline:

Một cơ sở lớp destructor nên là công khai và ảo, hoặc bảo vệ và nonvirtual

này có thể được phân loại như là một hành vi không xác định (chúng tôi nhận thức được rằng ~ D() sẽ không được gọi là chắc chắn)?

Hành vi không xác định theo tiêu chuẩn, thường dẫn đến phá hủy lớp có nguồn gốc và không dẫn đến rò rỉ bộ nhớ, nhưng không thích hợp để suy đoán sau khi có hành vi không xác định vì tiêu chuẩn không gaurantee bất cứ điều gì về vấn đề này.

C++ 03 Tiêu chuẩn: 5.3.5 Xóa

5.3.5/1:

Nhà điều hành xóa biểu hiện phá hủy một đối tượng có nguồn gốc nhất (1.8) hoặc mảng tạo bằng một biểu thức mới.
xóa biểu hiện:
:: chọn xóa đúc biểu
:: chọn delete [] đúc thể hiện

5.3.5/3:

Trong lần đầu tiên thay thế (xóa đối tượng), nếu kiểu tĩnh của toán hạng khác với kiểu động của nó, kiểu tĩnh phải là một lớp cơ sở của kiểu động của toán hạng và kiểu tĩnh sẽ có một destructor ảo hoặc hành vi là không xác định. Trong phương án thứ hai (xóa mảng) nếu loại năng động của các đối tượng được xóa khác với kiểu tĩnh của nó, hành vi này là undefined.73)

gì nếu ~D() trống. Nó có ảnh hưởng đến mã theo bất kỳ cách nào không?
Vẫn là hành vi không xác định theo tiêu chuẩn, Trình dẫn lớp dẫn xuất bị trống có thể làm cho chương trình của bạn hoạt động bình thường nhưng đó lại là khía cạnh thực hiện được xác định của một triển khai cụ thể, về mặt kỹ thuật, nó vẫn là hành vi không xác định.

Lưu ý rằng không có gaurantee ở đây mà không làm cho lớp dẫn xuất destructor ảo chỉ không dẫn đến cuộc gọi đến lớp dẫn xuất destructor và giả định này là không chính xác. Theo tiêu chuẩn, tất cả các phiên cược sẽ bị loại khi bạn bị vượt qua trong khu vực Hành vi Không xác định.

Lưu ý những gì anh ấy nói tiêu chuẩn về Hành vi không xác định.

C++ 03 Tiêu chuẩn: 1.3.12 hành vi undefined [defns.undefined]

hành vi, chẳng hạn như có thể phát sinh khi sử dụng một cấu trúc chương trình có sai sót hoặc dữ liệu sai sót, mà quốc tế này Tiêu chuẩn không áp đặt yêu cầu.Hành vi không xác định cũng có thể được mong đợi khi Tiêu chuẩn này loại bỏ mô tả về bất kỳ định nghĩa hành vi rõ ràng nào. [Lưu ý: phạm vi hành vi không xác định được cho phép từ bỏ hoàn toàn tình huống với kết quả không thể đoán trước, hoạt động trong khi thực hiện dịch hoặc thực hiện chương trình theo đặc tính của môi trường (có hoặc không có thông báo chẩn đoán) để chấm dứt bản dịch hoặc thực hiện (với việc phát hành một thông báo chẩn đoán). Nhiều cấu trúc chương trình sai lầm không tạo ra hành vi không xác định; họ được yêu cầu được chẩn đoán.]

Nếu chỉ destructor có nguồn gốc sẽ không được gọi là điều chỉnh bởi văn bản in đậm trong báo giá ở trên, rõ ràng là trái mở cho mỗi thực hiện.

+0

+1 cho std :: quotes; nhưng tôi vẫn không hiểu, tại sao tiêu chuẩn đặt nó là UB. Vì nó được đảm bảo rằng '~ D()' sẽ không được gọi. Hành vi được bảo đảm là UB? – iammilind

+2

@iammilind: * Vì nó được đảm bảo rằng ~ D() sẽ không được gọi *, nói ai? Tiêu chuẩn chỉ nói nếu destructor không phải là ảo thì nó là UB, destructor không được gọi là một hiệu ứng sau trong ** hầu hết các triển khai ** và nó không phải là gauranteed, cũng không yêu cầu theo tiêu chuẩn. –

+2

@iammilind Không nơi nào được đảm bảo rằng '~ D()' không được gọi. Các tiêu chuẩn nói rằng nó * undefined * những gì xảy ra trong trường hợp này và có thể bao gồm trình biên dịch bằng cách nào đó chèn ma thuật để làm cho '~ D()' được gọi là! Nó chỉ sau khi thực hiện v-table mà trong hầu hết các trình biên dịch các destructor có nguồn gốc sẽ không được gọi. –

7
  1. Behavior Không xác định
  2. (Một lưu ý đầu tiên, những deconstructors nói chung là không trống rỗng như bạn sẽ nghĩ. Bạn vẫn phải mổ xẻ tất cả các thành viên của bạn) Ngay cả khi deconstructor thực sự là rỗng (POD?), Sau đó nó vẫn còn phụ thuộc vào trình biên dịch của bạn. Nó không được xác định bởi tiêu chuẩn. Đối với tất cả các tiêu chuẩn quan tâm máy tính của bạn có thể thổi lên trên xóa.
  3. Behavior Không xác định

Có thực sự là không có lý do cho một destructor công phi ảo trong một lớp học mà có nghĩa là để được thừa hưởng từ. Nhìn vào số this article, Hướng dẫn số 4.

Sử dụng trình phá hủy không ảo được bảo vệ và shared_ptrs (chúng có liên kết tĩnh) hoặc trình phá hủy ảo công khai.

+0

Tại sao nó * undefined * ... Không phải là nó * cũng được xác định * rằng destructor sẽ không được gọi là chắc chắn? – iammilind

+0

Tôi đoán bạn có thể dựa vào thực tế là nó không gọi D. Nhưng trừ khi D thực tế là một lớp trống tôi khá chắc chắn điều này sẽ gây ra vấn đề như các thành viên của D không nhận được cuộc gọi deconstructor. – Lalaland

+1

Đúng. Nhưng câu hỏi của tôi là, mọi thứ sẽ xảy ra ** như mong đợi ** như, '~ D()' không được gọi, hàm hủy cho các thành viên của '~ D()' không được gọi và cứ thế ... ? – iammilind

2

Khi được xác nhận lại bởi những người khác, điều này hoàn toàn không được xác định bởi vì hàm hủy của cơ sở không phải là ảo và không ai có thể đưa ra bất kỳ tuyên bố nào. Xem this thread để tham khảo các tiêu chuẩn và thảo luận thêm.

(Tất nhiên, trình biên dịch cá nhân có quyền thực hiện những lời hứa nào đó, nhưng tôi đã không nghe bất cứ điều gì về điều đó trong trường hợp này.)

Tôi tìm thấy nó thú vị mặc dù, rằng trong trường hợp này tôi nghĩ rằng mallocfree được xác định tốt hơn trong một số trường hợp hơn newdelete. Có lẽ chúng ta nên sử dụng những thay :-)

Cho một lớp cơ sở và lớp dẫn xuất, không phải trong đó có bất kỳ phương pháp ảo, sau đây được định nghĩa:

Base * ptr = (Base*) malloc(sizeof(Derived)); // No virtual methods anywhere 
free(ptr); // well-defined 

Bạn có thể nhận được một ký ức rò rỉ nếu D có các thành viên thêm phức tạp, nhưng ngoài việc này là hành vi được xác định.

+0

Tôi nghĩ rằng xóa có thể được xác định rõ ràng cho những thứ như POD. Đã đến lúc đi lặn tiêu chuẩn. – Lalaland

+0

@EthanSteinberg, Ví dụ về chủ đề khác [liên kết một lần nữa] (http://stackoverflow.com/a/2065961/146041) được dựa trên POD, theo như sự hiểu biết của tôi. (Trên thực tế, nếu cấu trúc chỉ có chức năng không phải ảo, nó có thể vẫn được gọi là POD không?) –

+0

Vâng, nhưng tôi nghe tiêu chuẩn C++ mới đã làm khá nhiều công việc trong việc sửa đổi POD là gì, nhưng hóa ra tôi đã sai. Từ ngữ vẫn giống nhau, giống như không xác định như trước đây. – Lalaland

1

(Tôi nghĩ rằng tôi có thể xóa câu trả lời khác của mình.)

Mọi thứ về hành vi đó không xác định. Nếu bạn muốn có hành vi được xác định tốt hơn, bạn nên xem xét shared_ptr hoặc thực hiện một cái gì đó tương tự. Sau đây là định nghĩa hành vi, không phụ thuộc vào ảo-Ness của bất cứ điều gì:

shared_ptr<B> p(new D); 
    p.reset(); // To release the object (calling delete), as it's the last pointer. 

Bí quyết chính của shared_ptr là các nhà xây dựng templated.