EC读书笔记系列之8:条款13、14、15

条款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一样,它隐藏了客户不需要看的部分,但备妥客户需要的所有东西。

原文地址:https://www.cnblogs.com/hansonwang99/p/4935985.html