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 Cat
là noexcept
(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.
Nhân đôi bản sao của một bản sao của một ... – Xeo
Đây có phải là C++ 11 không? Tôi chưa bao giờ thấy nó trước –
@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