2011-12-02 43 views
8

thể trùng lặp:
When is a function try block useful?
Difference between try-catch syntax for functionMục đích của khối thử chức năng là gì?

Mã này ném một ngoại lệ int trong khi xây dựng các đối tượng Dog bên trong lớp UseResources. Trường hợp ngoại lệ int được khai thác bởi một try-catch khối và mã đầu ra bình thường:

Cat() 
Dog() 
~Cat() 
Inside handler 

#include <iostream> 
using namespace std; 

class Cat 
{ 
    public: 
    Cat() { cout << "Cat()" << endl; } 
    ~Cat() { cout << "~Cat()" << endl; } 
}; 

class Dog 
{ 
    public: 
    Dog() { cout << "Dog()" << endl; throw 1; } 
    ~Dog() { cout << "~Dog()" << endl; } 
}; 

class UseResources 
{ 
    class Cat cat; 
    class Dog dog; 

    public: 
    UseResources() : cat(), dog() { cout << "UseResources()" << endl; } 
    ~UseResources() { cout << "~UseResources()" << endl; } 
}; 

int main() 
{ 
    try 
    { 
     UseResources ur; 
    } 
    catch(int) 
    { 
     cout << "Inside handler" << endl; 
    } 
} 

Bây giờ, nếu chúng ta thay đổi định nghĩa của các nhà xây dựng UseResources(), với một trong đó sử dụng một function try block, như sau ,

UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {} 

kết quả là giống nhau

Cat() 
Dog() 
~Cat() 
Inside handler 

tức là, với kết quả chính xác như nhau.

Sau đó, mục đích của một function try block là gì?

+4

Nhân đôi bản sao của một bản sao của một ... – Xeo

+0

Đây có phải là C++ 11 không? Tôi chưa bao giờ thấy nó trước –

+0

@VJovic Tôi không biết khi nào điều này được giới thiệu bằng ngôn ngữ. Nhưng nó không phải là mới. – Belloc

Trả lời

9

Hãy tưởng tượng nếu UseResources được định nghĩa như thế này:

class UseResources 
{ 
    class Cat *cat; 
    class Dog dog; 

    public: 
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; } 
    ~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; } 
}; 

Nếu Dog::Dog() ném, sau đó cat sẽ bị rò rỉ bộ nhớ. Trở thành nhà xây dựng của UseResources không bao giờ hoàn thành, đối tượng chưa bao giờ được xây dựng hoàn chỉnh. Và do đó nó không có destructor của nó được gọi là.

Để ngăn chặn rò rỉ này, bạn phải sử dụng một chức năng cấp khối try/catch:

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...) 
{ 
    delete cat; 
    throw; 
} 

Để trả lời câu hỏi của bạn đầy đủ hơn, mục đích của một khối try/catch chức năng cấp trong nhà thầu là đặc biệt để làm loại dọn dẹp này. Các khối try/catch ở mức chức năng không thể ngoại lệ nuốt (những trường hợp thông thường có thể). Nếu họ bắt được thứ gì đó, họ sẽ ném nó một lần nữa khi họ đến cuối khối đánh bắt, trừ khi bạn sử dụng lại nó một cách rõ ràng với throw. Bạn có thể biến đổi một loại ngoại lệ thành một loại ngoại lệ khác, nhưng bạn không thể nuốt nó và tiếp tục như nó không xảy ra.

Đây là một lý do khác khiến giá trị và con trỏ thông minh nên được sử dụng thay cho con trỏ khỏa thân, ngay cả khi là thành viên của lớp. Bởi vì, như trong trường hợp của bạn, nếu bạn chỉ có giá trị thành viên thay vì con trỏ, bạn không phải làm điều này. Đó là việc sử dụng một con trỏ khỏa thân (hoặc dạng tài nguyên khác không được quản lý trong một đối tượng RAII) có tác dụng đến loại điều này.

Lưu ý rằng điều này là khá nhiều chỉ sử dụng hợp pháp các khối try/catch chức năng.


Các lý do khác không sử dụng khối thử chức năng. Đoạn mã trên bị hỏng một cách tinh tế.Hãy xem xét điều này:

class Cat 
{ 
    public: 
    Cat() {throw "oops";} 
}; 

Vì vậy, điều gì xảy ra trong công cụ xây dựng UseResources? Vâng, biểu thức new Cat sẽ ném, rõ ràng. Nhưng điều đó có nghĩa là cat chưa bao giờ được khởi tạo. Điều này có nghĩa là delete cat sẽ mang lại hành vi không xác định.

Bạn có thể thử để sửa chữa điều này bằng cách sử dụng một lambda phức tạp thay vì chỉ new Cat:

UseResources() try 
    : cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }()) 
    , dog() 
{ cout << "UseResources()" << endl; } 
catch(...) 
{ 
    delete cat; 
    throw; 
} 

Đó lý thuyết sửa chữa vấn đề, nhưng nó phá vỡ một bất biến giả của UseResources. Cụ thể là, UseResources::cat sẽ luôn là con trỏ hợp lệ. Nếu đó thực sự là một bất biến của UseResources, thì mã này sẽ thất bại vì nó cho phép việc xây dựng UseResources bất chấp ngoại lệ.

Về cơ bản, không có cách nào để làm cho mã này an toàn trừ khi new Catnoexcept (rõ ràng hoặc ngầm).

Ngược lại, điều này luôn luôn làm việc:

class UseResources 
{ 
    unique_ptr<Cat> cat; 
    Dog dog; 

    public: 
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; } 
    ~UseResources() { cout << "~UseResources()" << endl; } 
}; 

Nói tóm lại, nhìn một hàm cấp thử-block như một mã mùi nghiêm trọng.

+0

Thú vị. Không có ví dụ nào trong số các ví dụ mà tôi đã thấy cho đến nay với chức năng thử chặn các thứ dọn dẹp này đã xuất hiện. Nhưng trong ví dụ của bạn, chương trình sẽ hủy bỏ mọi việc, vậy thì vấn đề lớn với tính năng này là gì? Mục đích của việc dọn dẹp là gì nếu hệ điều hành vẫn hoạt động? – Belloc

+0

Xử lý ngoại lệ không phải là về làm việc thông qua thất bại đó là về xử lý thất bại một cách duyên dáng hơn. Tính năng này giúp bạn giải phóng tài nguyên một cách duyên dáng. Một ví dụ thực tế có thể là một khách hàng bị rơi và sử dụng nó để tài nguyên máy chủ miễn phí để nó không để nó không hoạt động được cho các máy khách khác. – AJG85

+0

@ AJG85 Nhưng 'terminate()' sẽ luôn được gọi với một khối thử chức năng, và trong trường hợp này, mọi tài nguyên sẽ được giải phóng bởi hệ điều hành. Điều đó có đúng không? – Belloc

2

Các khối thử chức năng thông thường có mục đích tương đối ít. Họ gần giống như một khối try bên trong cơ thể:

int f1() try { 
    // body 
} catch (Exc const & e) { 
    return -1; 
} 

int f2() { 
    try { 
    // body 
    } catch (Exc const & e) { 
    return -1; 
    } 
} 

Sự khác biệt duy nhất là cuộc sống chức năng-thử-block trong hàm-phạm vi lớn hơn một chút, trong khi việc xây dựng thứ hai sống trong hàm thân -scope - phạm vi trước đây chỉ nhìn thấy các đối số chức năng, sau này cũng biến cục bộ (nhưng điều này không ảnh hưởng đến hai phiên bản của khối thử).

duy nhất ứng dụng thú vị đi kèm trong một constructor hãy thử-block:

Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { } 

Đây là cách duy nhất mà ngoại lệ từ một trong những initializers thể được đánh bắt. Bạn không thể xử lý ngoại lệ, vì toàn bộ cấu trúc đối tượng phải vẫn không thành công (do đó bạn phải thoát khỏi khối catch với ngoại lệ, cho dù bạn muốn hay không). Tuy nhiên, cách duy nhất để xử lý ngoại lệ từ danh sách trình khởi tạo cụ thể.

Điều này có hữu ích không? Chắc là không. Có cơ bản không có sự khác biệt giữa một khối nhà xây dựng thử và sau đây, điển hình hơn "khởi-to-null-và-assign" mô hình, mà là chính nó khủng khiếp:

Foo() : p1(NULL), p2(NULL), p3(NULL) { 
    p1 = new Bar; 
    try { 
    p2 = new Zip; 
    try { 
     p3 = new Gulp; 
    } catch (...) { 
     delete p2; 
     throw; 
    } 
    } catch(...) { 
    delete p1; 
    throw; 
    } 
} 

Như bạn thấy, bạn có một unmaintainable , không thể nhầm lẫn. Một constructor-try-block sẽ còn tồi tệ hơn vì bạn thậm chí không thể biết được có bao nhiêu con trỏ đã được gán. Vì vậy, thực sự nó là chỉ hữu ích nếu bạn có chính xác hai số phân bổ có thể bị rò rỉ.Cập nhật: Nhờ đọc this question Tôi đã cảnh báo rằng thực tế bạn không thể sử dụng khối catch để xóa tài nguyên, vì việc tham chiếu đến đối tượng thành viên là hành vi không xác định. Vì vậy, [end update]

Tóm lại: Nó vô dụng.

+0

"Nó là vô ích." Trừ khi tất nhiên bạn muốn dịch ngoại lệ. –

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