使资源接受对象化管理
将资源托管于类的对象,使资源的生命周期协同于对象的生命周期,避免分散地分配回收资源,是 C++ 程序设计的重要方法。
动态分配的资源如果只能通过普通指针或变量访问,不受类对象的构造和析构等机制控制,则称为“无主”资源,极易产生泄漏或死锁等问题。应尽量使用容器、智能指针以及资源对应的类,避免直接使用 malloc、free、new、delete 等底层资源管理接口。
示例:
int* p = new int[8]; // Non-compliant, ownerless
.... // If any exception is thrown,
// or any wrong jump occurs, the memory leaks
struct X { int* p; };
X x;
x.p = new int[8]; // Non-compliant, no destructor, ‘x’ is not an owner
....
delete[] p; // Non-compliant, explicit delete
delete[] x.p; // Non-compliant
例中用不受析构函数控制的指针保存 new 表达式的结果,以及对应的 delete 表达式均不符合要求。
应将资源托管于类对象,在构造函数中进行资源分配,在析构函数中完成资源回收,落实拷贝、移动接口,并由成员函数提供访问资源的方法,这样便使资源的生命周期协同于对象的生命周期,将资源管理简化为对象管理,如:
class Owner {
int* p;
size_t n;
public:
Owner(size_t n) : p(new int[n]()), n(n) {} // Compliant
~Owner() { delete[] p; } // Compliant
....
};
例中 Owner 类掌管动态分配的内存资源,如果按下列方式使用:
int foo() {
Owner obj(8); // Safe and brief, ‘obj’ is the owner of the resource
if (cond1) {
return 0; // No need to release memory manually
}
if (cond2) {
throw Exception(); // Ditto
}
....
}
则称 Owner 类的对象 obj 拥有资源的所有权,当 obj 的生命周期结束后,相关资源会被自动回收,从而避免了在复杂的流程分枝中手工回收资源,有效提高了资源管理的安全性。注意,由于篇幅有限,示例代码未展示拷贝、移动构造函数和相关赋值运算符,在实际代码中均应妥善实现,可参见 ID_violateRuleOfFive 的进一步介绍。
本规则是 C++ 核心资源管理准则 — “RAII(Resource Acquisition Is Initialization)”的量化体现,在构造函数中与成员绑定的底层分配接口以及在析构函数中对应的底层回收接口可不受本规则约束,但应优先使用容器或智能指针。
资源的所有权可以发生转移,但应保证转移前后均有类对象负责管理资源,并且在转移过程中不会产生异常,可参见 ID_throwInConstructor、ID_exceptionUnsafe 的进一步介绍。
下面给出使用智能指针管理资源的例子:
void foo(const TCHAR* path) {
WIN32_FIND_DATA ffd;
HANDLE h = FindFirstFile(path, &ffd); // Non-compliant, ownerless
....
CloseHandle(h); // Is it right?
}
例中 FindFirstFile 是用于资源分配的底层 Windows API,返回的资源句柄对应“无主”资源,需要显式回收,应使其接受类对象的管理:
class MY_FIND_DATA
{
struct HANDLE_DELETER
{
using pointer = HANDLE;
void operator()(pointer p) { FindClose(p); }
};
WIN32_FIND_DATA ffd;
unique_ptr<HANDLE, HANDLE_DELETER> uptr;
public:
MY_FIND_DATA(const TCHAR* path): uptr(FindFirstFile(path, &ffd)) {}
....
HANDLE handle() { return uptr.get(); }
};
将 FindFirstFile 及其相关数据封装成一个类,由 unique_ptr 对象保存 FindFirstFile 的结果,FindClose 是资源的回收方法,将其作为 unique_ptr 对象的组成部分,使资源可以被自动回收。