资源:内存,文件描述器,互斥锁,图形界面的字体和笔刷,数据库链接,socket等。
十三:Use objects to manage resources. 以对象管理资源
class Investment{...};
Investment* createInvestment();
void f()
{
Investment* pInv = createInvestment();
//...
delete pInv;
}
为了确保createInvestment返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f,该对象的析构函数会自动释放那些资源。把资源放进对象内,我们便可依赖c++的“析构函数自动调用机制”确保资源被释放。
改写f():
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
...
//调用factory函数 一如以往地使用pInv 经由auto_ptr地析构函数自动删除pInv
}
注:获得资源后立刻放进管理对象内。 管理对象运用析构函数确保资源被释放。
然而auto_ptr的所有权模型表明同时只能有一个auto指针指向对象。
std::auto_ptr<Investment> pInv1(createInvestment());//pInv1指向createInvestment返回物
std::auto_ptr<Investment> pInv2(pInv1);//现在pInv2指向对象 pInv1被设为null
pInv1 = pInv2;//现在pInv1指向对象,pInv2被设为Null
可以用引用计数模型指针替代它。
总结:
为了防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常时较佳选择,因为其copy行为比较直观。若选择
auto_ptr,复制动作会使它指向null。
十四:Think carefully about copying behaviour in resource-managing classes。在资源管理类中小心coping行为
void lock(Mutex* pm);
void unlock(Mutex* pm);
class Lock
{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm)
{
lock(mutexPtr);//获得资源
}
~Lock() { unlock(mutexPtr); }//释放资源
private:
Mutex *mutexPtr;
};
//客户对Lock的用法符合RAII方式
Mutex m; //定义所需的互斥器
...
{ //建立一个区块用来定义critical section
Lock m1(&m);//锁定互斥器
... //执行critical section内的操作
} //在区块最末尾, 自动解除互斥器锁定
如果复制了Lock对象。
Lock m11(&m); //锁定m
Lock m12(m11); //复制
当一个RAII对象被复制,我们可以选择
1.禁止复制。 见前 private copying
2.引用计数。
我们可以改变mutexPtr的类型,用shared_ptr封装起来,但shared_ptr的缺省行为是“当引用计数为0时删除其所指物。”
我们可以给shared_ptr指定一个删除器(function),当引用计数为0时被调用。
见例子:
class Lock
{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm, unlock)
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
};
3.复制底部资源。进行deep copy.
4.转移底部资源的所有权。
总结:
1.复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
2.普遍而常见的RAII class copying 行为是:抑制 copying 引用计数。
十五:Provide access to raw resources in resource-managing classes.在资源管理类中提供对原始资源的访问
例如tr1::shared_ptr和auto_ptr提供一个get成员函数,用来执行显式转换,它会返回智能指针内部的原始指针
注:APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的方法。
对原始资源的访问可能由显式转换或隐式转换。一般来说显示转换比较安全,但隐式转换对客户比较方便。
十六:Use the same form in corresponding uses of new and delete 成对使用new和delete时要采用相同形式
std::string* stringArray = new std::string[100];
delete stringArray;
使用new 时 1.分配内存 2.构造函数被调用 使用delete时 1.析构函数被调用 2.内存被释放
delete执行时需要知道删除的那个指针所指的是单一对象或对象数组,决定它执行析构的数量。
通过显式声明 delete[]告诉程序释放的是一个数组结构。
结论:
new[] 和 delete[]对应
new和delete对应。
需要注意的是typedef 需要清楚以何种方式释放。
十七:Store newed objects in smart pointers in standalone statements.以独立语句将newed对象置入智能指针。
考虑一个例子:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
proccessWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
考虑这个调用,
编译器可能的操作顺序:
1.调用priority
2.执行"new Widget"
3.调用tr1::shared_ptr构造函数
也有可能是这样的操作顺序:
1.执行 "new Widget"
2.调用 priority
3.调用 tr1::shared_ptr 构造函数
在这种调用顺序下,如果对priority的调用导致异常,会导致资源泄露。
为了避免这个风险,考虑:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, PRIORITY());
这样写可以规定顺序,防止风险。
总结:
以独立语句将newed对象存储于智能指针内。