使资源接受对象化管理
2.4 ID_ownerlessResource
将资源托管于类的对象,使资源的生命周期协同于对象的生命周期,避免分散处理分配与回收等问题,是 C++ 程序设计中的重要方法。
动态申请的资源如果只能通过普通指针或变量访问,不受对象的构造和析构等机制控制,则称为“无主”资源,极易产生泄漏或死锁等问题。应尽量使用标准库提供的容器、智能指针以及资源对应的类,避免直接使用 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 Mgr {
int* p;
public:
Mgr(size_t n): p(new int[n]) {}
~Mgr() { delete[] p; }
};
Mgr m(8); // Compliant, ‘m’ is the owner of the resource
例中 m 对象负责资源的分配与回收,称 m 对象拥有资源的所有权,相关资源的生命周期与对象的生命周期一致,有效避免了资源泄漏或错误回收等问题。针对成员的 new、delete 可不受本规则限制,但应优先使用容器或智能指针。
资源的所有权可以发生转移,但应保证转移前后均有对象负责管理资源,并且在转移过程中不会产生异常。进一步理解对象化管理方法,可参见“RAII(Resource Acquisition Is Initialization)”等机制。
另外,底层资源管理接口也不应直接在业务代码中使用,如:
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 对象的组成部分,使资源可以被自动回收。