条款13:以对象管理资源
获得资源后立刻放进资源管理对象内 (Resource Acquisition Is Initialization RAII)
管理对象运用析构函数确保资源被释放
为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
两个常被使用的RAII class分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,赋值动作会使它指向null
文中的auto_ptr已被C++11所摒弃,考虑用unique_ptr代替
条款14:在资源管理类中小心copying行为
class Lock{
public:
explicit Lock(Mutex* pm):mutexPtr(pm){
lock(mutexPtr);
}
~Lock(){unlock(mutexPtr);}
private:
Mutex* mutexPtr;
}
这样虽然可以保证客户在使用Lock时符合RAII,但是当发生复制时就会有问题
Lock m1(&m);
Lock m2(m1);
所以此时应该采用一定的方式去保证复制时不出问题:
1、禁止复制
2、对底层资源使用引用计数法
3、复制底部资源,即深拷贝
4、转移底部资源
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法
条款15:在资源管理类中提供对原始资源的访问
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment* pi);
这种情况下,使用资源管理类造成API无法调用。
所以资源管理类应该提供对原始资源的访问。
1、显式转换
class Investment{
public:
InvestmentHandle* get() const{return mInvest;}
}
通过get函数返回原始资源
2、隐式转换
class Investment{
public:
operator InvestmentHandle*() const{return mInvest;}
}
通过重载operator()提供隐式转换来返回原始资源。
APIs往往要求访问原始资源,所以每一个RAII class应该提供一个取得原始资源的办法
对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换更安全,但隐式转换更方便
条款16:成对使用new和delete时要采取相同形式
用new[]申请内存的话就要用delete[]去释放。用new申请内存的话就用delete去释放
条款17:以独立语句将newed对象置入智能指针
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority)
调用时如果采用这种方式:
processWidget(new Widget, priority());
编译不通过,因为tr1::shared_ptr的构造函数是explicit,不允许隐式转换。
可以改为下面的方式
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
但是此方式有可能会出现问题:
因为编译器对 priority()、new Widget和tr1::shared_ptr构造函数三个的执行顺序不一定,有可能出现先执行new Widget 再执行priority()最后执行tr1::shared_ptr构造函数。这时如果priority()出现异常,那个new Widget返回的指针就变成了野指针。
所以要这么写
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
以独立语句将newed对象置入智能指针内。如果不这样做,一旦抛出异常,有可能会造成内存泄漏。
虽然本章内容大部分已被C++11解决,但是本章的思想还是值得学习的。