资源(文件描述器,互斥锁,图形界面中的字型和笔刷,数据库连接,以及网络sockets)就是一旦使用了它,将来必须还给系统。本章一开始是基于对象的资源管理方法,而管理内存的对象必须知道如何适当而正确的工作。
条款13:以对象管理资源
void f() {
Investment * pInv = createTnvestment(); //调用factory函数,返回指针,指向Investment继承体系内的动态分配对象
...
delete pInv; //释放pInv所指对象
}
缺点:“...”区域中的一个过早的return语句或者中途抛出异常,若delete在循环中,有可能遇到continue,break或者goto语句而过早退出。无论哪种情况,我们最终泄露的不只是投资对象的那块对象,还包括那些投资对象所保存的任何资源,如string对象。
改进:我们需要将资源放进对象内,当控制流离开f时,该对象的析构函数会自动释放那些资源。如auto_ptr是个类指针对象。
<span style="font-size:10px;">void f(){
std::auto_ptr<Investment> pInv(creatInvestment());
...
} </span> //经由auto_ptr的析构函数自动删除pInv
以对象管理资源的两个关键想法:1,获得资源后立刻放进管理对象,如调用createInvestment获得资源,放进auto_ptr对象中。 2,管理对象运用析构函数确保资源被释放。如管理对象离开作用域,其析构函数会被自动调用。
std::auto_ptr<Investment> pInv1(creatInveatment());
std::auto_ptr<Investment> pInv2(pInv1); //现在pInv2指向对象,pInv1被设为null
pInv1 = pInv2; //现在pInv1指向对象,pInv2被设为null
以上是auto_ptr的复制属性,导致没有一个以上的auto_ptr同时指向同一资源,可以引入“引用计数型智慧指针”RCSP,但RCSPs无法打破环状引用(两个没有被使用的对象互指,好像“被使用”。)
void f(){
...
std::tr1::shared_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment的返回物
std::tr1::shared_ptr<Investment> pInv2(pInv1); //pinv1和pInv2指向同一个Inveatment对象
pInv1 = pInv2; //同上,无任何改变
...
} //pInv1和pInv2被销毁,她们所指的对象也就自动被销毁
auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作,故std::auto_ptr<std::string> aps(new std::string[10])从原则上看应该不合法。而在Boost中,boost::scoped_array和boost::shared_array classes都提供了这种分配数组的行为。我们也可以制作自己的资源管理类。最后,creatInvestment返回的“未加工指针”可能导致资源泄露,为防止忘记调用delete,可以将删除器绑定在智能指针上。
条款14:在资源管理类中小心coping行为
RAII:资源取得时机便是初始化时机,auto_ptr和tr1::shared_ptr用在heap资源上,而对于非heap资源,有时候需要建立自己的资源管理类。以下是用Lock类(资源管理类)来管理Mutex类(资源:互斥锁)
void lock(Mutex *pm); //锁定pm所指的互斥器
void unlock(Mutex *pm); //将互斥器解除锁定
class Lock{
public:
explicit Lock(Mutex *pm):mutexPtr(pm) { lock(mutexPtr); } //构造函数获得资源
~Lock() { unlock(mutexPtr); } //析构函数释放资源
private:
Mutex *mutexPtr;
};
Mutex m; //定义你需要的互斥器
...
{ //建立一个区块用定义critical section,临界区
Lock ml(&m); //锁定互斥器
... //执行critical section内操作
} //在区块最末尾,调用析构函数自动解除互斥器锁定
Lock ml1(&m); //锁定ml
Lock ml2(ml1); //将ml1复制到ml2身上,
对于最后一句,我们选择以下两种可能:1,禁止复制,因为很少能够合理拥有“同步化基础器物”的副本。使用以下方法来禁止复制:
class Lock:private Uncopyable{
public:
...
};
2,对底层资源使用“引用计数法”。有时候我们希望保有资源,直到它的最后一个使用者被销毁。故对于Lock类,可内含一个tr1::shared_ptr成员变量,由于我们用上一个Mutex,我们想要的释放动作是解除锁定而非删除。而tr1::shared_ptr允许指定所谓的“删除器”,当引用参数为0时便可调用,实现如下所示:
class Lock{
public:
explicit Lock(Mutex *pm):mutex(pm, unlock) //以某个Mutex来初始化shared_ptr,并以unlock函数为删除器
{ lock(mutexPtr.get()); }
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
};
此时Lock class没有必要再声明析构函数,因为mutexptr的析构函数会在互斥器的引用次数为0时自动调用tr1::shared_ptr的删除器。
对对象进行复制时,可1,复制底部资源,进行的是深度复制,如string对象的拷贝,须将成员变量中的指针指向的内存资源进行复制。2,转移底部资源的拥有权。资源的拥有权会从被复制物转移到目标物,如auto_ptr指针。
条款15:在资源管理类中提供对原始资源的访问
可以倚赖资源管理类来处理与资源之间的所有互动。
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment *pi); //返回投资天数
int day = daysHeld(pInv);
此时没办法通过编译,因为函数需要将RAII class对象转换为其所含的原始资源的指针。以下有两种方法:
1,显式转换,tr1::shared_ptr和auto_ptr都提供了一个get成员函数来返回原始指针 int days = dayHeld(pInv.get());
2,隐式转换,tr1::shared_ptr和auto_ptr都重载了指针取值操作符(*和->),他们允许隐式转换成底部原始指针:
class Investment{
public:
bool isTaxFree() const;
...
};
Investment *createInvestment();
std::tr1::shared_ptr<Investment> pil(createInvestment());
bool taxable1 = !(pi1->isTaxFree()); //由智能指针转换为原始指针,再调用其成员函数,返回值为bool型
...
std::auto_ptr<Investment> pi2(createInvestment());
bool taxable2 = !((*pi2).isTaxFree());
同理,对于大量与字体有关的C API,处理的是FontHandles,那么需要大量将Font转换为FontHandle。
1,显式转换,这个Font class类会增加泄露字体的可能性
2,隐式转换,增加了“非故意之类型转换”的机会
对原始资源的访问可能经由显式转换或隐式转换,一般而言显式转换比较安全,但隐式转换对客户比较方便。
条款16:成对使用new和delete时要采取相同的形式
条款17:以独立语句将newed对象置入智能指针