资源管理
所谓资源就是:一旦用了它,就必须归还给系统。C++中最常使用的资源就是动态分配内存(若分配内存不归还,就会造成内存泄漏)。其它常见的资源包括文件描述器(fd)、互斥锁、数据库连接、网络sockets。
条款13:以对象管理资源
例:投资类型
class Investment {...}; //投资类型继承体系中的root class
factory函数供应特定的Investment对象
Investment* createInvestment(); //返回指针,指向Investment继承体系内的动态分配对象。调用者有责任删除
f函数履行删除的责任
void f()
{
Investment* pInv = createInvestment(); //调用factory函数
...
delete pInv; //释放pInv所指对象
}
在程序...区域内的语句过早return或者抛出异常可能导致最后delelte被略过,
为确保createInvestment返回的资源总是被释放,需要:把资源放进对象内,便可依赖C++的"析构函数自动调用机制”确保资源被释放。
auto_ptr是个"类指针对象“,也就是所谓的”智能指针“,其析构函数自动对其所指对象调用delete。下面示范:
void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); //调用fatory函数,一如既往使用pInv,
... //auto_ptr的析构函数自动删除pInv
}
"以对象管理资源“的两个关键想法:
-获得资源后立刻放进管理对象(managing object)内。以上代码中createInvestment返回的资源当做其管理者auto_ptr的初值。
-管理对象运用析构函数确保资源被释放。不论控制流如何如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。
注意:由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr指向同一对象。
auto_ptr有个性质:
若通过copy构造函数或copy assignment操作符赋值它们。它们会编程null,而复制所得的指针将取得资源的唯一拥有权
std::auto_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment的返回物
std::auto_ptr<Investment> pInv2(pInv1); //现在pInv2指向对象,pInv1被设为Null
pInv1 = pInv2; //pInv1指向对象,pInv2被设为Null
auto_ptr并非管理动态内存分配资源的神兵利器。例如:STL容器要求其元素发挥”正常的“复制行为,因此auto_ptr不合适。
替代方案是”引用计数型智慧指针“---shared_ptr:
void f()
{
...
std::tr1::shared_ptr<Investment>
pInv1(createInvestment());
std::tr1::shared_ptr<Investment>
pInv2(pInv1); //pInv1和pInv2指向同一对象
pInv1 = pInv2;
}
auto_ptr和shared_ptr都在其析构函数内做delete而不是delete[],所以在动态分配的array上使用auto_ptr和shared_ptr是个馊主意。
请记住:
-为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
-两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是最佳选择,因为其copy行为比较直观。auto_ptr的复制行为会使它们指向null.
条款14:在资源管理类中小心copying行为。
使用C API函数处理类型为Mutex的互斥器对象,有lock和unlock两个函数:
void lock(Mutex* pm); //锁定pm所指的互斥器
void unlock(Mutex* pm); //将互斥器解除锁定
为防止忘记将一个被锁住的Mutex对象解锁,建立class用来管理机锁。这样的class基本结构由RAII守则支配:资源在构造期间获得,在析构期间释放。
class Lock {
public:
explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr); } //构造函数,获得资源,锁定互斥器
~Lock() { unlock(mutexPtr); } //释放资源
private:
Mutex *mutexPtr;
};
Mutex m; //定义需要的互斥锁
...
{ //建立区块用来定义critical section
Lock m1(&m); //锁定互斥器
... //执行操作
} //在区块末尾自动解除互斥器锁定
当一个RAII对象被复制,会发生什么事?大多数时候选择一下两种可能:
---禁止复制。许多时候允许RAII对象被复制并不合理。则禁止copying操作,将copying操作声明为private。
---对底层资源祭出”引用计数法“(reference-count)。将资源的”被引用数“递增,tr1::shared_ptr便是如此。
将Mutex* 改为 tr1::shared_ptr<Mutex>,便可实现Lock的reference counting。
对shared_ptr指定删除器,当引用次数为0时不是释放对象,而是解除锁定。
class Lock {
public:
explicit Lock(Mutex* pm) : mutexPtr(pm,unlock) //以某个Mutex初始化shared_ptr,并以unlock函数为删除器
{
Lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; //shared_ptr
}
//本例的Lock class未声明析构函数,class的析构函数(自己定义或者编译器生成)会自动调用其non-static成员变量(例中mutexPtr)的析构函数,即在shared_ptr的引用次数为0时调用删除器(unlock)
请记住:
-复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为
-普遍而常见的RAII class copying行为是:抑制copying,施行引用计数法。
条款15:在资源管理类中提供对原始资源的访问
假如希望某个函数处理Investment对象,像这样:
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment* pi); //返回投资天数
int days = daysHeld(pInv); //错误,因为daysHeld需要Investment*指针,传给它的却是tr1::shared_ptr<Investment>对象。
shared_ptr和auto_ptr都提供一个get成员函数用来执行显式转换,会返回智能指针内部的原始指针:
int days = daysHeld(pInv.get()); //将pInv内的原始指针传给daysHeld
tr1::shared_ptr和auto_ptr重载了指针取值操作符(operator->和operator*),允许隐式转换至底部原始指针。
请记住:
-APIs往往要求访问原始资源,所以每一个RAII class应该提供一个”取得其所管理之资源“的办法。
-对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。
条款16:成对使用new和delete时要采取相同形式
std::string* stringArray = new std::string[100];
...
delete stringArray;
错误:stringArray所含的100个string对象中的99个不太可能被适当删除,因为析构函数很可能没有被调用。
new:
-内存被分配出来 => 针对此内存会有一个构造函数被调用。
delete:
-针对此内存会有一个析构函数被调用 => 内存被释放出来
delete最大问题在于:
即将被删除的内存之内究竟存有多少对象?即将被删除的那个指针,所指的是单一对象或对象数组?
当你对着指针执行delete的时候,这些信息由你告诉它!:
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1; //删除一个对象
delete [] stringPtr2; //删除一个由对象组成的数组
请记住:
如果你调用new时使用[],你必须在对应调用delete时也使用[].如果你调用new时没有使用[],那么也不该在对应调用delete时使用[]。
条款17:以独立语句将newed对象置于智能指针
假设有个函数处理程序的优先权,另一个函数在某动态分配所得的Widget上进行某些带有优先权的处理:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
谨记条款13的智慧铭言“以对象管理资源”,对动态分配来的Widget运用智能指针(这里采用tr1::shared_ptr)
processWidget(new Widget, priority()); //错误,不能通过编译
因为shared_ptr构造函数是explicit。所以无法将new Widget得到的指针隐式转换为tr1::shared_ptr.
改正:
processWidget(std::tr1:;shared_ptr<Widget>(new Widget),priority());
在processWidget调用之前有三件事(参数列表):
-调用priortity
-执行 new Widget
-调用tr1::shared_ptr构造函数
但是以什么次序执行呢?不确定 (new Widget 一定是在tr1::shared_ptr构造函数之前)
若最终是这样的操作序列:
-执行 new Widget
-调用priortity
-调用tr1::shared_ptr构造函数
在调用priority中导致异常,则new Widget返回的指针将遗失 ,引发资源泄漏。
避免此类问题的方法是:使用分离语句
1.创建Widget
2.将它置于智能指针内,然后传给processWidget
std::tr1::shared_ptr<Widget> pw(new Widget); //在单独语句内智能指针存储newed所得对象。
processWidget(pw,priority()); //这个调用动作绝不至于造成泄漏
请注意:
-以独立语句将newed对象存储与智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
完