2013-02-05 36 views
9

Tôi có một lớp có thể ném một ngoại lệ trong hàm tạo của nó. Làm thế nào tôi có thể khai báo một thể hiện của lớp đó trong một khối try/catch, trong khi vẫn làm cho nó có sẵn trong phạm vi bên phải?Cách tiếp cận RAII để bắt các ngoại lệ của nhà xây dựng

try { MyClass lMyObject; } 
catch (const std::exception& e) { /* Handle constructor exception */ } 

lMyObject.DoSomething(); // lMyObject not in scope! 

Có cách nào khác để thực hiện điều này, trong khi tôn trọng thành ngữ RAII?

Tôi không muốn sử dụng phương thức init() để xây dựng hai giai đoạn. Điều duy nhất tôi có thể đưa ra là:

MyClass* lMyObject; 

try { lMyObject = new MyClass(); } 
catch (const std::exception& e) { /* Handle constructor exception */ } 

std::shared_ptr<MyClass> lMyObjectPtr(lMyObject); 
lMyObjectPtr->DoSomething(); 

Hoạt động OK, nhưng tôi không hài lòng với con trỏ thô trong phạm vi và con trỏ. Đây có phải chỉ là một mụn cóc C++ khác không?

+2

Chuyển cuộc gọi phương thức DoSomething() sang khối thử? – milleniumbug

+1

** Không bao giờ ** bắt ngoại lệ bằng bản sao. Gắn bó với * tham chiếu liên tục *. –

+1

Trong ví dụ thứ hai, nếu hàm tạo đã ném, 'lMyObject' là một con trỏ chưa được khởi tạo. – aschepler

Trả lời

5

Nếu một nhà xây dựng ném điều đó có nghĩa là đối tượng không thể khởi tạo và do đó nó không thể bắt đầu sự tồn tại của nó.

MyClass* lMyObject; 
try { lMyObject = new MyClass(); } 
catch (std::exception e) { /* Handle constructor exception */ } 

Ở phía trên nếu các nhà xây dựng ném một ngoại lệ, lMyObject còn lại chưa được khởi tạo, nói cách khác, con trỏ chứa một giá trị không xác định.

Xem classic Constructor Failures cho một lời giải thích chi tiết:

Chúng ta có thể tóm tắt các mô hình xây dựng C++ như sau:

Hoặc:

(a) trở về nhà xây dựng thông thường bằng cách tiếp cận kết thúc của nó hay một câu lệnh trả về và đối tượng tồn tại.

Hoặc:

(b) Các lối ra constructor bằng cách phát ra một ngoại lệ, và đối tượng không chỉ không phải bây giờ tồn tại, nhưng không bao giờ tồn tại.

Không có khả năng nào khác.

0

Bạn không cần phải sử dụng shared_ptr, sử dụng unique_ptr:

std::unique_ptr<MyClass> pMyObject; 
try { pMyObject.reset(new MyClass()); } 
catch (std::exception &e) { /* Handle constructor exception */ throw; } 
MyClass &lMyObject = *pMyObject; 

lMyObject.DoSomething(); 

Rõ ràng, đó là trách nhiệm của bạn để đảm bảo rằng chương trình không thuộc thông qua các khối catch mà không cần một trong hai initialising pMyObject, hoặc thoát khỏi chức năng (ví dụ: qua return hoặc throw).

Nếu có, bạn có thể sử dụng Boost.Optional để tránh sử dụng bộ nhớ heap:

boost::optional<MyClass> oMyObject; 
try { oMyObject.reset(MyClass()); } 
catch (std::exception &e) { /* Handle constructor exception */ throw; } 
MyClass &lMyObject = *oMyObject; 

lMyObject.DoSomething(); 
+0

Nó trỏ đến con trỏ 'NULL' nếu một ngoại lệ được ném ra. –

+0

@MaximYegorushkin phụ thuộc hoàn toàn vào những gì '/ * Xử lý ngoại lệ constructor * /'. – ecatmur

+0

Một thử nghiệm tốt cho lời khuyên của bạn là thay thế '/ * Xử lý ngoại lệ constructor * /' bằng một chuỗi rỗng. Lời khuyên của bạn thất bại trong thử nghiệm này. –

-1

Bạn có thể thiết lập MyClass của copy constructor để chấp nhận đầu vào thùng rác, do đó hiệu quả khai báo một con trỏ với tuyên bố của bạn của đối tượng. Sau đó, bạn có thể manually call constructor mặc định bên trong khối try:

MyClass lMyObject(null); // calls copy constructor 
try { 
    new (lMyObject) MyClass(); // calls normal constructor 
} 
catch (const std::exception& e) { /* Handle constructor exception */ } 

lMyObject.DoSomething(); 
+0

Nó gọi hàm tạo của 'MyClass' hai lần, hủy một lần . –

+0

Phải - để mô hình này hoạt động, bạn sẽ phải viết trình tạo bản sao của mình sao cho nó không phân bổ bất kỳ tài nguyên nào. Đó là một chút hackish. –

1

Constructors là để đưa một đối tượng vào một nhà nước thống nhất và thiết lập bất biến lớp.Cho phép một exception để thoát khỏi một constructor có nghĩa là một cái gì đó trong constructor đó đã thất bại trong việc hoàn thành, vì vậy bây giờ đối tượng đang ở trong một trạng thái lạ nào đó (có thể bao gồm cả rò rỉ tài nguyên bây giờ). Kể từ khi constructor của đối tượng chưa hoàn thành, trình biên dịch sẽ không gây ra destructor của nó được gọi ra. Có lẽ những gì bạn đang tìm kiếm là để bắt ngoại lệ bên trong constructor. Giả sử nó không được rethrown, điều này sẽ làm cho constructor hoàn thành việc thi hành, và đối tượng bây giờ đã được định dạng đầy đủ.

2

Cách tốt nhất để viết mã của bạn là thế này: -

MyClass lMyObject; 
lMyObject.DoSomething(); 

Không trys, sản lượng đánh bắt, hoặc con trỏ.

Nếu người xây dựng ném, thì DoSomething không thể gọi. Đó là đúng: Nếu các nhà xây dựng đã ném, sau đó đối tượng không bao giờ được xây dựng.

Và, quan trọng là không bắt (hoặc thậm chí bắt/rút lại) ngoại lệ trừ khi bạn có thứ gì đó mang tính xây dựng để làm với chúng. Hãy để ngoại lệ làm công việc của họ và gợn lên cho đến khi một cái gì đó mà biết làm thế nào để xử lý chúng có thể làm công việc của mình.

+0

Cảm ơn bạn đã gợi ý, tôi nghĩ đây là một điểm tốt. –

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