条款13:以对象管理资源
- 为防止内存资源泄露,当申请到内存资源后,最好立即以对象方式进行封装,即RAII的方式。比如,我们常用的shared_ptr,书中提到的auto_ptr已经被弃用了,因为它的拷贝操作会转移所有权,在容器类操作时会发生问题。
class A {
public:
int val;
};
// 这里返回A实例的智能指针,就能有效防止内存资源泄露
std::shared_ptr<A> CreateA()
{
return std::make_shared<A>();
}
- 用对象封装内存资源可以防止内存泄漏的原理是当对象生命周期结束后,它会自动调用对象的析构函数,这时候我们可以在析构函数释放之前申请的内存资源。
条款14:在资源管理类中小心copying行为
- copying行为包括拷贝构造、赋值运算符、移动构造。针对资源管理类,要小心处理这些copying的行为。因为每种情况需要的操作不同。
- 禁止相关copying函数,比如我们常用的RAII类型锁,unqiue_lock、lock_guard类型锁,很显然我们不能复制一个存在的RAII类型锁,所以我们应该禁止这些函数。在C++11之后,这种操作变得非常简单,书中提到的方法有点落伍且复杂了。
class A {
public:
A() {std::cout << "construct" << std::endl;}
A(const A& rhs) = delete; // 禁止拷贝构造
A(A&& rhs) = delete; // 禁止移动构造
A& operator = (const A& rhs) = delete; // 禁止赋值运算
};
- 增加引用计数,即我们常用的shared_ptr就是该类型操作的代表。
- 复制底部资源,即深度copy。
- 转移底部资源,废弃的auto_ptr类型即是该类操作的代表。
条款15:在资源管理类中提供对原始资源的访问
- 当外部的处理函数需要以原始资源的方式进行处理,这个时候就需要资源管理类提供获取底层原始资源的访问通道。
- 书中提到可以通过隐式转换的方式将一个管理类对象转换为原始资源类对象,虽然对程序员来说,代码编写变得简单,但存在资源悬垂的隐患,所以建议还是显式的获取比较稳妥。
条款16:new和delete采用相同的形式
这条规则很简单也很直接,即new内存资源的时候使用了[],则delete的时候也要使用[],如下:
char* p = new char[100];
...
delete [] p;
条款17:以独立语句将newed资源置于智能指针
之所以这样做,主要是为了防止不易察觉的内存泄露,考察如下情况:
class A {
};
Process(std::shared_ptr<A>(new A), SubProcess());
Process函数中第一个参数是A类型的智能指针,第二个参数是调用SubProcess函数获取的另外一个对象。这个过程中包含了三个操作:
(1)申请A资源
(2)构造shared_ptr<A>对象
(3)调用SubProcess函数
由于编译器不一定严格按照上述的流程来执行,它可能先执行(3)再执行(2),这个时候如果在执行(3)的时候发生了异常,则(1)操作产生的A资源将会产生泄露,因为还没有将A类型指针装进智能指针对象。
解决的方法:
class A {
};
auto sp = std::shared_ptr<A>(new A);
Process(sp, SubProcess());