2013-07-15 37 views
14

Sau ngày từ này: Is the destructor called when a delegating constructor throws?Bộ nhớ có được tự động thu hồi khi một nhà xây dựng ủy nhiệm ném không?

class X 
{ 
public: 
    X()  {}; 
    X(int) : X() { throw std::exception(); } 
    X(double)  { throw std::exception(); } 
    ~X(); 
}; 

gì về bộ nhớ động? Thông thường, một ngoại lệ trong hàm tạo có nghĩa là đối tượng không được xây dựng đầy đủ và do đó bộ nhớ được dọn dẹp và không bị mất.

Nhưng đối số trong câu hỏi trước là đối tượng được xây dựng hoàn chỉnh (hoặc được khởi tạo đầy đủ) sau khi ủy nhiệm hoàn tất. Điều này ảnh hưởng đến việc đòi lại bộ nhớ như thế nào? Tôi hy vọng rằng bộ nhớ vẫn được dọn dẹp!

int main() 
{ 
    new X(5);  // new called 
        // delete called because delegate completed. 
        // I assume: 
        //  Memory re-claimed (because constructor did not complete) 
        //  I assume the C++11 standard adjusted to compensate. 
        //  As a constructor did complete. 
} 

So quá:

int main() 
{ 
    new X(5.0);  // new called 
        // Delete **NOT** called 
        // Memory re-claimed (because constructor did not complete) 
} 

Nếu nhớ được dọn dẹp, sau đó định nghĩa về khi bộ nhớ là dọn dẹp cần phải được thay đổi từ C++ 03 spec. Hành vi đã thay đổi như thế nào?

+2

Tiêu chuẩn nói rằng "nếu constructor không ủy nhiệm cho một đối tượng đã hoàn thành việc thực thi và một hàm khởi tạo ủy nhiệm cho đối tượng đó thoát với một ngoại lệ, hàm hủy của đối tượng sẽ được gọi ra. " Vì vậy, destructor được gọi. Nếu điều đó không trả lời câu hỏi của bạn, nó không đủ rõ ràng, ít nhất là với tôi. –

+1

@DavidSchwartz: Không bị hủy hoại nhưng được khai hoang. Trong C++ 03 một ngoại lệ trong hàm khởi tạo không bị rò rỉ, bởi vì một ngoại lệ mà thoát khỏi hàm khởi tạo rời khỏi đối tượng không được tạo thành hoàn toàn. Nhưng ở đây đối tượng được hình thành đầy đủ. Câu hỏi của tôi thực sự là cách xử lý đã thay đổi từ C++ 03. –

+0

@DavidSchwartz: Bạn nên đăng câu trả lời đó (lý tưởng là tham khảo). Tôi thấy đó là một điều nguy hiểm mặc dù ... –

Trả lời

21

Nếu hàm tạo được gọi là new ném ngoại lệ thì bộ nhớ được phân bổ bởi new sẽ tự động được deallocated. Các nhà thầu phân quyền không thay đổi gì trong vấn đề này.

Nếu bất kỳ phần nào của việc khởi tạo đối tượng mô tả ở trên chấm dứt bằng cách ném một ngoại lệ và một chức năng deallocation phù hợp có thể được tìm thấy, chức năng deallocation được gọi để giải phóng bộ nhớ, trong đó các đối tượng đã được xây dựng

                                                                                                                                                                                                              — C++ 11 [expr.new] 5.3.4/18

Các 'bất kỳ phần nào của việc khởi tạo đối tượng' được mô tả bao gồm cả constructor gọi và đánh giá các biểu thức được truyền cho hàm tạo.

Ngoài ra, hành vi này được xác định giống hệt nhau trong tiêu chuẩn C++ 98 [C++ 98 5.4.3/17]. Sự khác biệt duy nhất mà các nhà xây dựng ủy nhiệm tạo ra là nếu mô hình tinh thần của bạn trước đây dựa trên đối tượng đang được xây dựng hoàn chỉnh hay không. Với các hàm tạo ủy nhiệm không còn tương đương với đặc tả thực tế khi xảy ra deallocation.


Trong ví dụ đầu tiên của bạn:

new X(5); 

Trình tự các sự kiện là:

  • chức năng phân bổ gọi là
  • X (int) gọi
    • X () được gọi (và thoát thành công)
    • X (int) ném một ngoại lệ
    • ~ X() được gọi
  • X (int) thoát qua ngoại lệ
  • chức năng deallocation gọi vì khởi tạo đối tượng không thành công
  • ngoại lệ conti nues để tuyên truyền thường

Với ví dụ thứ hai

new X(5.0); 
  • chức năng phân bổ gọi là
  • X (double) gọi
  • X (double) không thành công với một ngoại lệ
  • chức năng deallocation được gọi vì khởi tạo đối tượng không thành công
  • ngoại lệ conti nues để tuyên truyền thường

Bạn có thể quan sát hành vi này bằng cách thay thế các chức năng phân bổ và deallocation:

#include <iostream> 
#include <cstdlib> 
#include <stdexcept> 
#include <new> 

void *operator new(std::size_t s) { 
    if (void *ptr = std::malloc(s)) { 
     std::cout << "allocation\n"; 
     return ptr; 
    } 
    throw std::bad_alloc{}; 
} 

void operator delete(void *ptr) noexcept { 
    if (ptr) { 
     std::cout << "deallocation\n"; 
     std::free(ptr); 
    } 
} 

struct S { 
    S() {}; 
    S(int) : S{} { throw std::exception(); } 
    S(double) { throw std::exception(); } 
    ~S() { std::cout << "destructor\n"; } 
}; 

int main() { 
    std::cout << "test 1\n"; 
    try { 
     new S(1); 
    } catch(...) { 
     std::cout << "exception caught\n"; 
    } 

    std::cout << "test 2\n"; 
    try { 
     new S(1.); 
    } catch(...) { 
     std::cout << "exception caught\n"; 
    } 
} 

Kết quả đúng của chương trình này là:

test 1 
allocation 
destructor 
deallocation 
exception caught 
test 2 
allocation 
deallocation 
exception caught 
+1

Có. Tôi nghĩ rằng chìa khóa là trở về từ các nhà xây dựng chính là _not_ kết thúc khởi tạo. –

+0

Chỉnh sửa của bạn đã để lại nội dung nào đó. Trong 'new X (5)', vì 'X :: X()' đã thành công, 'X :: ~ X()' sẽ được gọi trước khi deallocation. –

+0

@BenVoigt đã thêm – bames53

Các vấn đề liên quan