资源的概念是一旦使用了该资源,将来就必须还给系统,不然就会产生不确定的行为。
C++中最常使用的资源就是动态分配内存,但还有其他的资源比如文件描述器、互斥锁、图形界面的字型和笔刷、数据库连接以及网络socket。
以对象管理资源
以对象管理资源的话,主要是有两种关键的想法:
获得资源之后立刻放进管理对象(RAII机制)
管理对象运用析构函数来确保资源被释放—在析构函数中的资源释放可能会抛出异常,需要把异常吞掉。
//RAII
class Investment{...};
//使用工场函数返回一个特定的Investment对象
Investment* createInvestment();
//其实感觉RAII的使用之一就是多使用智能指针
void f(){
std::auto_ptr<Investment> pInv1(createInvestment()); //使用智能指针的话会经由auto_ptr的析构函数来自动删除pInv
std::auto_ptr<Investment> pInv2(createInvestmemt());
pInv1 = pInv2; //此时pInv1指向对象,pInv2为null
...
}
//但是由于auto_ptr被销毁的时候会自动删除掉它所指之物,所以不允许将auto_ptr同时指向同一个对象
//对auto_ptr使用copying函数(copy构造函数与copy assignment操作符)会将auto_ptr变成null(以获得资源的唯一拥有权)
//但有的时候会要求发挥正常的复制行为,这样的话,就可以使用tr1::share_ptr
//即引用计数型智慧指针(RCSPs),持续追踪有多少对象指向某笔资源,在无人指向它的时候自动删除(无法打破环状引用)
void f(){
std::tr1::share_ptr<Investment> pInv1(createInvestment);
std::tr1::share_ptr<Investment> pInv2(createInvestment);
pInv1 = pInv2; //两者指向同一个对象
}
auto_ptr与trl::share_ptr的析构函数做的是delete行为而非delete[ ]行为,所以不能将这两个智能指针用于array上面(但这个行为会通过编译)。
void f(){
std::auto_ptr<std::string> aps(new std::string[10]); //错误行为!!!
}
//如果需要使用,建议使用boost::scoped_array和boost::share_array
在资源管理类中小心copying行为
上述的使用auto_ptr与tr1::share_ptr来进行管理资源是基于该资源是heap-base的。如果对于非heap-base上的资源来说这些智能指针就不适合作为资源管理者,此时可以建造一个资源管理的类来进行资源管理。
//互斥锁的资源管理类
class Lock{
public:
explicit Lock(Mutex* pm) : mutexPrt(pm)
{ lock(mutexPtr); } //获得资源
~Lock() { unlock(mutexPtr); } //释放资源
private:
Mutex *mutexPtr;
};
由于复制RAII类的对象的时候使用的是深度拷贝,这时候连同类所管理的资源也一并复制,所以资源的copying行为决定了RAII对象的copying行为。
常见的应对RAII类的copying行为是抑制copy和施用引用计数法。
//使用继承一个无法复制的类来禁止复制
class Lock: private Uncopyable{
...
};
//使用引用计数法---类似于tr1::share_ptr,实际操作也是在类中使用一个tr1::share_ptr的成员变量来实现
//使用引用计数法的话可以保有资源直到最后一个使用者
class Lock{
public:
//此处的mutexPtr第二个参数unlock是指定unlock为删除器,auto_ptr并不能指定删除器
explicit Lock(Mutex* pm) : mutexPtr(pm,unlock)
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
};
在资源管理类中提供原始资源的访问
有的时候我们需要将RAII对象转化为原始的资源,书上提出了两个做法,显式转换与隐式转换:
//tr1::share_ptr和auto_ptr都提供了一个get函数成员,用来执行显示的转化
std::auto_ptr<Investment> pInv(createInvestment());
pInv.get(); //返回一个智能指针内部的原始指针副本
//所有的智能指针也重载了指针取值操作符(operate->和operate*)的功能,他们允许隐式转换成原始指针
class Investment{
public:
isTest();
};
std::auto_ptr<Investment> pInv1(createInvestment());
pInv1->isText();
(*pInv1).isText();
APIs往往要求访问原始资源,所以每个RAII类应该提供一个返回资源的方法。
对于这种返回资源的方法来说,一般是要显示转换会比隐式转换安全一点(避免增加发生错误的机会)。
成对使用new与delete
如果使用new就要用delete。
如果new中使用[ ],那么相应的delete也要使用[ ]。
std::string* stringArray = new std::string[10];
delete[] stringArray; //如果使用delete的话,后面的9个string对象可能不会删掉。
以独立语句将newed对象置入智能指针
C++编译器对于参数的核算顺序是不确定的,所以如果在智能指针中进行new操作的话可能会导致内存泄漏。
processWidget(std::trl::share_ptr<Widget>(new Widget),priority());//可能会导致资源泄露
std::tr1::share_ptr<Widget> pw(new Widget);
processWidget(pw,priority()); //不会造成泄露