条款13 以对象管理资源
记住:
★为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放
★两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,∵其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null
------------------------------------------------------------------------
为确保资源总是被释放,应将资源放进对象内,当控制流离开后,该对象的dtor会自动释放那些资源。以对象管理资源的两个关键想法:
(1)获得资源后立刻放进管理对象;
(2)管理对象运用dtor确保资源被释放
auto_ptr特殊性质:通过copying函数复制它们,它们会变成NULL,而复制所得的指针将取得资源的唯一拥有权:
std::auto_ptr<Investment> pInv1( createInvestment() );
std::auto_ptr<Investment> pInv2( pInv1 ); //现pInv2指向对象,而pInv1为null!!!
可见受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它。
auto_ptr的一个替代方案是“引用计数型智能指针”,如tr1::shared_ptr。即该智能指针持续追踪共有多少个对象指向某笔资源,并在无人指向它时自动删除该资源。∴其copying行为正常很多:
std::tr1::shared_ptr<Investment> pInv2( pInv1 ); //pInv1和pInv2指向同一个对象
auto_ptr和shared_ptr两者都在其destructor内做delete而不是delete[]。这意味着在动态分配而得的array身上使用以上两者十分不好。尽管如此,其仍然能通过编译:
std::auto_ptr<std::string> aps( new std::string[10] );
条款14 在资源管理类(自己建立的)中小心copying行为
记住:
★复制RAII对象必须一并复制它所管理的资源,∴资源的copying行为决定RAII对象的copying行为
★普遍而常见的RAII class copying行为是:抑制copying、引用计数法。不过其他行为也都可能被实现
--------------------------------------------------------------------------------
举例:
为确保不忘记将一个被锁住的Mutex解锁,可建立一个class来管理机锁。这样的class基本结构由RAII守则支配:资源在构造期间获得,在析构期间释放
1 class Lock { 2 public: 3 explicit Lock( Mutex *pm ) : mutexPtr( pm ) { 4 lock( mutexPtr ); //获得资源 5 } 6 7 ~Lock() { 8 unlock( mutexPtr ); //释放资源 9 } 10 private: 11 Mutex *mutexPtr; //raw资源 12 };
客户使用时:
1 Mutex m; 2 ... 3 { //建立一个区块来定义critical section 4 Lock ml( &m ); //锁定互斥器 5 ... //执行critical section内的操作 6 } //区块最末尾自动解除互斥器锁定
但若Lock对象被复制,会发生何事???
当一个RAII对象被复制,会有两种处理方式:
方式一:禁止复制
将copying操作声明为private,所以对Lock而言看起来如下:
class Lock : private Uncopyable {
...
};
方式二:对底层资源祭出“引用计数法”
希望保有资源直到它的最后一个使用者(某对象)被销毁。复制RAII对象时应将该资源的被引用数递增。tr1::shared_ptr便是如此。
对方法二,通常RAII类中只要内含一个tr1::shared_ptr即可实现reference-counting copying行为。而此处要定制tr1::shared_ptr的“删除器”,当引用次数为0时调用(∵tr1::shared_ptr的默认行为是当引用次数为0时删除其所指物):
1 class Lock { 2 public: 3 explicit Lock( Mutex *pm ) : mutexPtr( pm, unlock ) { 4 5 lock( mutexPtr.get() ); //get函数返回sp内部raw指针(的复件) 6 } 7 8 private: 9 std::tr1::shared_ptr<Mutex> mutexPtr; //用智能指针替换raw pointer 10 };
此处无需dtor!因为编译器生成的dtor会自动调用其non-static成员(本例的mutexPtr)的dtor,而mutexPtr的dtor会在互斥器的引用次数为0时自动调用tr1::shared_ptr的删除器(本例为unlock)。
条款15 在资源管理类中提供对原始资源的访问
记住:
★APIs往往要求访问原始资源,∴每个RAII class应提供一“取得其所管理之资源”的办法
★对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换较安全,但隐式转换对客户较方便。
---------------------------------------------------------------------------------
有两方法可以将RAII class对象转换为其所内含之原始资源:显式和隐式转换:
举例:
1 class Font { 2 3 public: 4 explicit Font( FontHandle fh ):f( fh ) {} //获得资源 5 ~Font() { releaseFont(f); } //释放资源 6 private: 7 FontHandle f; //原始字体资源 8 };
假设有大量与字体相关的C API处理的是FontHandles,那么就需将Font对象转换为FontHandle。Font class有两种做法:
方法一:提供显式转换函数:
1 class Font { 2 3 public: 4 ... 5 FontHandle get() const { return f; } //显式转换函数 6 ... 7 };
客户使用时:
1 void changeFontSize( FontHandle f, int newSize ); //C API,需要原始资源 2 Font f( getFont() ); //获取字体资源,Font是资源管理类 3 int newFontSize; 4 ... 5 changeFontSize( f.get(), newFontSize ); //明白地将Font转换为FontHandle
方法二:提供隐式转换函数:
1 class Font { 2 3 public: 4 ... 5 operator FontHandle() const { return f; } //隐式转换函数 6 ... 7 };
客户使用时:
1 changeFontSize( f, newFontSize ); //会将Font隐式转换为FontHandle
注:
tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,其会返回智能指针内部的原始指针(的复件)。而且tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针:
std::tr1::shared_ptr<Investment> pi1( createInvestment() ); //令tr1::shared_ptr管理一笔资源
bool taxlabel = !( pi1->isTaxFree() ); //经由operator->访问资源
//或bool taxlabel = !( (*pi1).isTaxFree() ); //经由operator*访问资源
补充:
RAII class内的那个返回原始资源的函数,确实是与“封装”思想矛盾。但RAII并非为了封装某物而存在,所以也没关系。就像很多设计良好的classes一样,它隐藏了客户不需要看的部分,但备妥客户需要的所有东西。