2013-08-28 66 views
5
class XX 
{ 
public: 
    static unsigned s_cnt; 
    XX() 
    { 
     ++ s_cnt; 
     std::cout << "C XX " << s_cnt << "\n"; 

     if (s_cnt > 2) 
      throw std::exception(); 
    } 

    //private: 
    ~XX() 
    { 
     std::cout << "~ XX\n"; 
    } 
}; 
unsigned XX::s_cnt = 0; 

int main() 
{ 
    try 
    { 
     XX *xx = new XX[10]; 
    } catch (...) 
    { 
     std::cout << "Exc\n"; 
    } 
} 

Output:Tại sao không phải là destructors được gọi là khi một ngoại lệ uncaught là ném trong C + + trong quá trình tạo mảng?

C XX 1 
C XX 2 
C XX 3 
~ XX 
~ XX 
Exc 

Nhưng khi tôi loại bỏ try-catch, tôi thấy:

C XX 1 
C XX 2 
C XX 3 
terminate called after throwing an instance of 'std::exception' 
    what(): std::exception 
zsh: abort  ./a.out 

Tại sao C++ gọi hàm hủy trong trường hợp đầu tiên nhưng không phải trong thứ hai?

Trả lời

13

Khi bạn không bắt ngoại lệ (tức là nó trở thành một ngoại lệ không bị bắt và chấm dứt chương trình của bạn), C++ không cung cấp cho bạn bất kỳ đảm bảo rằng trình phá hủy thực sự được gọi.

Điều này cho phép các trình biên dịch mất nhiều thời gian để thực hiện xử lý ngoại lệ. Ví dụ, GCC tìm kiếm đầu tiên cho một trình xử lý. Nếu nó không thể tìm thấy một, nó hủy bỏ ngay lập tức, bảo toàn thông tin ngăn xếp đầy đủ để gỡ lỗi. Nếu nó tìm thấy một, nó thực sự thư giãn ngăn xếp, phá hủy các đối tượng, cho đến khi nó đến lúc xử lý. Đó là lý do tại sao bạn không nhìn thấy đầu ra: chương trình hủy bỏ trước khi phá hủy bất kỳ đối tượng nào.

+1

Bạn có thể thêm rằng lý do cho điều này là để bạn không mất thông tin trạng thái trong kết xuất lõi. –

+0

Điểm tốt, được thêm vào. –

3

Khi bạn ném ngoại lệ từ một hàm tạo, tiêu chuẩn xử lý đối tượng là không được xây dựng. Bạn không phá hủy cái gì đó không tồn tại, phải không?

Trong thực tế, thậm chí ví dụ đơn giản này không làm việc như bạn đề nghị:

struct A { 
    A() {throw std::exception();} 
    ~A() {std::cout << "A::~A()" << std::endl;} 
}; 

int main() { 
    A a; 
} 

này chấm dứt với một ngoại lệ còn tự do, nhưng không in "A :: ~ A()".

Nếu bạn nghĩ về nó, đó là cách duy nhất có thể để đưa ra một ngữ nghĩa cho nó.

Ví dụ, chúng ta có thể sử dụng loại của chúng tôi A như một đối tượng thành viên của một lớp B:

struct B { 
    B() : m_a(),m_p(new int) {} 
    ~B() {delete m_p;} 

    A m_a; 
    int * m_p; 
}; 

Rõ ràng B::B() ném ngay lập tức (và thậm chí không khởi m_p). Nếu một giả thuyết C++ tiêu chuẩn bắt buộc để gọi B::~B() trong trường hợp này, destructor không có cách nào để biết nếu m_p đã được khởi tạo hay chưa.

Nói cách khác, việc ném ngoại lệ từ một hàm tạo có nghĩa là đối tượng không bao giờ tồn tại và thời gian tồn tại của nó chưa bao giờ bắt đầu. Điều này GotW là khá rõ ràng về nó.

Phần thưởng: điều gì sẽ xảy ra nếu trong định nghĩa B chúng tôi đổi thứ tự m_am_p? Sau đó, bạn bị rò rỉ bộ nhớ. Đó là lý do tại sao tồn tại một cú pháp cụ thể để bắt ngoại lệ trong quá trình khởi tạo các đối tượng thành viên.

+0

mới XX [10] xây dựng từng đối tượng một. Hai đối tượng đầu tiên được xây dựng mà không có bất kỳ vấn đề gì. Sau đó, nó cố gắng để xây dựng thứ 3 và thất bại. Sau đó, nó gọi destructor cho XX [1] và XX [0], nhưng không XX [2]. Trong thực tế, bạn chỉ có 2 dòng '' XX' trong đầu ra của bạn, chứ không phải là 3. – sbabbi

+0

Tôi đang đề cập đến trường hợp thứ hai, nơi khối 'try' đã bị loại bỏ và không có destructor nào được gọi, mặc dù các đối tượng đang được xây dựng. Bạn không giải quyết phần đó của câu hỏi. – juanchopanza

+0

Tôi đã bỏ lỡ phần chỉnh sửa nơi bạn thay đổi tiêu đề câu hỏi, điều xấu của tôi. Phần thứ hai được giải quyết bằng câu trả lời @Sebastian Redl. Nhưng nó không có gì để làm với thực tế là bạn đang làm việc với mảng. – sbabbi