条款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解决,但是本章的思想还是值得学习的。
|