2009-07-09 30 views
13

Tôi chỉ mới bắt đầu với RAII trong C++ và thiết lập một trường hợp thử nghiệm nhỏ. Hoặc mã của tôi bị nhầm lẫn sâu sắc, hoặc RAII không hoạt động! (Tôi đoán nó là cái cũ).C++ RAII không hoạt động?

Nếu tôi chạy:

#include <exception> 
#include <iostream> 
class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

int main(void) { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 

ngoại trừ nhận xét ra tôi nhận được:

A 1 constructed 
A 2 constructed 
A 2 destructed 
A 1 destructed 

như mong đợi, nhưng ngoại trừ tôi nhận được:

A 1 constructed 
A 2 constructed 
terminate called after throwing an instance of 'std::exception' 
    what(): std::exception 
Aborted 

nên đối tượng của tôi không bị phá hủy mặc dù chúng không nằm trong phạm vi. Đây không phải là toàn bộ cơ sở cho RAII.

Con trỏ và chỉnh sửa được đánh giá cao!

+5

Bạn cũng đã tìm thấy lỗi trong C++! =) – Eric

+0

trường hợp cạnh thú vị! –

+2

Bạn đã phá vỡ RAII: ( – rpg

Trả lời

6

Bạn có một ngoại lệ chưa được thực hiện trong chính, có nghĩa là một cuộc gọi để chấm dứt. Hãy thử điều này:

int main(void) 
{ 
    try 
    { 
     A a1(1); 
     A a2(2); 
     throw std::exception(); 
     return 0; 
    } 
    catch(const std::exception & e) 
    { 
     return 1; 
    } 


} 
+0

Vì vậy, tôi phải luôn luôn bọc chính trong try/catch khi sử dụng RAII? – John

+2

Tôi nghĩ main() là khác nhau. Hãy thử đặt mọi thứ vào một hàm mà bạn gọi từ số chính() –

+1

Không, chỉ khi bạn làm điều gì đó có thể ném một ngoại lệ – Alex

3

Bạn không xử lý ngoại lệ đúng cách, vì vậy ứng dụng của bạn sẽ thoát trước khi các đối tượng nằm ngoài phạm vi.

Tôi sẽ giải thích thêm một chút. Nếu một ngoại lệ "bong bóng" lên đến chính ngăn xếp là unwound (chỉnh sửa). Ngay cả việc chuyển mã sang hàm phụ sẽ không khắc phục được sự cố này. ví dụ:

 1 #include <exception> 
     2 #include <iostream> 
     3 
     4 void test(); 
     5 
     6 class A { 
     7  public: 
     8   A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
     9   ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
    10  private: int i_; 
    11 }; 
    12 
    13 
    14 int main(void) { 
    15  test(); 
    16  return 0; 
    17 } 
    18 
    19 void test(){ 
    20    A a1(1); 
    21    A a2(2); 
    22   throw std::exception(); 
    23 } 

Mã trên sẽ không giải quyết vấn đề. Cách duy nhất để giải quyết vấn đề này là bọc ngoại lệ được ném vào một khối try-catch. Điều này sẽ giữ cho ngoại lệ không đạt được chính, và dừng việc chấm dứt xảy ra trước khi các đối tượng đi ra khỏi phạm vi.

+0

Đây là câu trả lời đúng, -1? Có thật không? – Alex

+0

Ngay cả khi bạn di chuyển mã sang một chức năng khác nếu bạn không có thử/nắm bắt nó sẽ bong bóng chính và gây ra cùng một vấn đề. Vì vậy, "chính" không phải là đặc biệt cả. – Alex

+0

Bạn đang sai về "hành vi không xác định". Các hành vi của một ngoại lệ mà không có một handler được định nghĩa trong tiêu chuẩn, ngoại trừ việc có hay không stack là unwound được thực hiện xác định. –

20

Bạn không có trình xử lý cho ngoại lệ của mình. Khi điều này xảy ra tiêu chuẩn nói rằng std :: chấm dứt được gọi, mà lần lượt gọi hủy bỏ. Xem phần 14.7 trong Ngôn ngữ lập trình C++, ấn bản thứ 3.

+0

Bạn thực sự có thể thiết lập chức năng chấm dứt của riêng bạn, nhưng tôi không biết rằng đó là một khả năng thực sự hữu ích. –

+2

Vâng, nó có thể được sử dụng, nói, để đăng nhập chấm dứt một tập tin cụ thể hoặc gửi một email cho ai đó. –

+0

Như RaphaelPS đã chỉ ra, Viết chức năng chấm dứt của riêng bạn là hữu ích nếu bạn muốn đăng nhập hoặc chấm dứt tài nguyên toàn cầu. Bạn không thể, tuy nhiên, phục hồi từ chấm dứt. Trình xử lý chấm dứt không có đối số, không có giá trị trả lại và được mong đợi thoát khỏi ứng dụng. –

17

Vấn đề là main có trạng thái đặc biệt. Khi một ngoại lệ được ném từ đó, ngăn xếp không thể có ý nghĩa vô hiệu, ứng dụng chỉ gọi số std:terminate thay thế.

Và sau đó, điều này có ý nghĩa lý do tại sao các biến không nằm ngoài phạm vi. Chúng tôi chưa thực sự rời khỏi phạm vi mà chúng được khai báo là. Chuyện gì xảy ra có thể được coi là tương đương với điều này:

int main(void) { 
    A a1(1); 
    A a2(2); 
    std::terminate(); 
} 

(Tôi tin rằng nó được thực hiện xác định liệu hàm hủy được gọi là trong trường hợp này mặc dù, vì vậy trên một số nền tảng, nó sẽ làm việc như bạn mong đợi)

+0

Tại sao việc triển khai đó được xác định? Nó sẽ không có ý nghĩa hơn nếu các nhà thầu luôn được gọi? C++ đôi khi có thể ngớ ngẩn ... – Zifre

+2

Không ngớ ngẩn - thực tế. Việc buộc các destructors (không phải các hàm tạo) được gọi là luôn luôn sẽ giới hạn các cách mà EH và stack unrolling có thể được thực thi. –

+3

Nó có thể là ngớ ngẩn, nhưng nó cũng là thực dụng. Đặc tả C++ cố tránh giới hạn những người triển khai thực hiện. Lý tưởng nhất, họ muốn nó có thể tạo ra một sự thực thi chuẩn C++ trên mọi CPU * và * mọi hệ điều hành *. Và kể từ khi chức năng chính trong một ý nghĩa đánh dấu ranh giới giữa OS và C++, họ không muốn giả định quá nhiều về nó. Bạn nói đúng, trong thế giới thực, sẽ thật tuyệt nếu họ không để lại chi tiết cụ thể này cho việc triển khai. Chúng ta biết rằng bây giờ, nhưng nó đã rõ ràng vào năm 1998, khi ngôn ngữ đã được chuẩn hóa chưa? Họ không muốn vẽ mình vào một góc – jalf

5

Như những người khác đã chỉ ra, bạn đã có một ngoại lệ vô tình, mà các cuộc gọi chấm dứt(). Nó được định nghĩa thực hiện (xem tiêu chuẩn, 15.3 đoạn 9 và 15.5.1 đoạn 2) liệu các destructor có được gọi trong trường hợp này hay không, và định nghĩa trong việc thực hiện của bạn rõ ràng là "Không, chúng sẽ không". (Nếu chấm dứt() được gọi vì bất kỳ lý do nào khác ngoài việc ném một ngoại lệ mà không có một trình xử lý, các destructor sẽ không được gọi.)

4

Một đối tượng của bạn không bị hủy bởi vì std :: chấm dứt đang được gọi.

std :: chấm dứt được gọi khi ngoại lệ không được giải quyết rò rỉ ngoài chính. Nếu bạn quấn mã của bạn trong một try/catch (ngay cả khi bắt chỉ tăng lại), bạn sẽ thấy hành vi mà bạn đang mong đợi.

6

Nếu ngoại lệ thoát chính() đó là thời gian thực hiện được xác định ngăn xếp là unwound.

thử

int main() 
{ 
    try 
    { 
     doWork(); // Do you experiment here. 
    } 
    catch(...) 
    { /* 
     * By catching here you force the stack to unwind correctly. 
     */ 
     throw; // re-throw so exceptions pass to the OS for debugging. 
    } 
} 
0

Kể từ khi ngoại lệ không được xử lý bởi thời gian nó đạt main(), nó kết quả trong một cuộc gọi đến std :: chấm dứt(), bạn về cơ bản có tương đương với

int main(void) { 
    A a1(1); 
    A a2(2); 
    exit(1); 
} 

Cấu trúc không được bảo đảm để được gọi trong trường hợp chương trình chấm dứt trước khi chúng nằm ngoài phạm vi. Đối với một lỗ ở RAII, hãy xem xét:

int main(void) { 
    A *a1 = new A(1); 
} 
+1

Ví dụ thứ hai không phải là một lỗ hổng trong RAII; đó là một ví dụ về việc không sử dụng RAII. –

1

Những người khác đã gợi ý đặt một try/catch bên main() để xử lý này, trong đó hoạt động tốt. Vì một lý do nào đó, tôi tìm thấy 'chức năng-thử-block' hiếm khi được sử dụng để trông đẹp hơn, điều làm tôi ngạc nhiên (tôi nghĩ nó sẽ trông quá lạ). Nhưng tôi không nghĩ rằng có bất kỳ lợi thế thực sự:

int main(void) 
try 
{ 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 
catch (...) 
{ 
    throw; 
} 

Một vài khó khăn được rằng vì nó ít được sử dụng rất nhiều các nhà phát triển có được ném cho một vòng lặp khi họ nhìn thấy nó, và VC6 cuộn cảm vào nó nếu đó là một xem xét.

0

Mã sau hoạt động.

#include <exception> 
#include <iostream> 

class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

void test() { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
} 

int main(void) { 
try { 
    test(); 
} catch(...) { 
} 
    return 0; 
} 
Các vấn đề liên quan