Effective C++笔记: 资源管理(一)

条款13:以对象来管理资源

为了确保资源总能被释放,我们需要将资源放入一个类中,这个类的析构函数在控制流程离开其作用域的时候会自动释放资源。

 

std::auto_ptr:

注意:当一个 auto_ptr 被销毁的时候,会自动删除它所指向的东西,所以不要让超过一个的 auto_ptr 指向同一个对象非常重要。如果发生了这种事情,那个对象就会被删除超过一次,而且会让你的程序通过捷径进入未定义行为。为了防止这个问题,auto_ptrs 具有不同寻常的特性:拷贝它们(通过拷贝构造函数或者拷贝赋值运算符)就是将它们置为空,拷贝的指针被设想为资源的唯一所有权。

auto_ptr的拷贝构造如下:

auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()

       : _Myptr(_Right.release())

              {      // construct by assuming pointer from _Right auto_ptr

              }

_Ty *release() _THROW0()

              {      // return wrapped pointer and give up ownership

              _Ty *_Tmp = (_Ty *)_Myptr;

       _Myptr = 0;

       return (_Tmp);

              }

既置当前的_MyprtNULL,并将其复制到新的实例中去

std::auto_ptr<Investment>                 // pInv1 points to the
  pInv1(createInvestment());              // object returned from
                                          // createInvestment

std::auto_ptr<Investment> pInv2(pInv1);   // pInv2 now points to the
                                          // object; pInv1 is now null

pInv1 = pInv2;                            // now pInv1 points to the
                                          // object, and pInv2 is null

STL 容器要求其内含物能表现出正常的拷贝行为,所以 auto_ptrs 的容器是不被允许的。

 

std::tr1:: shared_ptr

这里的代码看上去和使用 auto_ptr 的几乎相同,但是拷贝 shared_ptrs 的行为却自然得多:

void f()
{
  ...

  std::tr1::shared_ptr<Investment>          // pInv1 points to the
    pInv1(createInvestment());              // object returned from
                                            // createInvestment

  std::tr1::shared_ptr<Investment>          // both
pInv1 and pInv2 now
    pInv2(pInv1);                           // point to the object

  pInv1 = pInv2;                            // ditto - nothing has
                                            // changed
  ...
}                                           // pInv1 and pInv2 are
                                            // destroyed, and the
                                            // object they point to is
                                            // automatically deleted

因为拷贝 tr1::shared_ptrs 的工作符合预期,它们能被用于 STL 容器以及其它和 auto_ptr 的非正统的拷贝行为不相容的环境中。

 

auto_ptr tr1::shared_ptr 都在它们的析构函数中使用 delete,而不是 delete []。(Item 16 描述两者的差异。)这就意味着将 auto_ptr tr1::shared_ptr 用于动态分配的数组是个馊主意,可是,可悲的是,那居然可以编译:

std::auto_ptr<std::string>           // bad idea! the wrong
  aps(
new std::string[10]);          // delete form will be used

std::tr1::shared_ptr<int> spi(new int[1024]);    // same problem

可以用boost::scoped_array boost::shared_array 两个类来实现这个功能

 

总结:

为了防止资源泄漏,使用 RAII 对象,在 RAII 对象的构造函数中获得资源并在析构函数中释放它们。

两个通用的 RAII tr1::shared_ptr auto_ptrtr1::shared_ptr 通常是更好的选择,因为它的拷贝时的行为是符合直觉的。拷贝一个 auto_ptr 是将它置为空。

 

Item 14: 谨慎考虑资源管理类的拷贝行为

当一个 RAII 对象被拷贝的时候应该发生什么?

1.  禁止拷贝。(将拷贝构造设为私有)。

2.  对底层的资源引用计数。(like  std::tr1::shared_ptr

3.  拷贝底层的资源。在这种情况下,拷贝一个资源管理对象也要同时拷贝被它隐藏的资源。拷贝之后有2份资源。

4.  传递底层资源的所有权,like std::auto_prt

 

Item 15: 在资源管理类中提供对原始资源的访问(raw resources

当我们使用std::tr1::shared_ptr<Investment> pInv(createInvestment()); 这样的形式来包装一个指针时,在实际使用中,大部分的API,如

int daysHeld(const Investment *pi);        // return number of days
                                           // investment has been held

会要求一个Investment*的参数。此时,不能直接传递pInv

 

因此,需要给资源管理类设计一个访问原始资源的接口。通常的方法有显示转换和隐式转换:

 

显示转换:

tr1::shared_ptr auto_ptr 都提供一个 get 成员函数进行显示转换,也就是说,返回一个智能指针对象内部的原始指针:

int days = daysHeld(pInv.get());    // fine, passes the raw pointer
                                    // in pInv to daysHeld

tr1::shared_ptr auto_ptr 也都重载了指针取值操作符(pointer dereferencing operators)(operator-> operator*),而这样就允许隐式转换到底层的原始指针(raw pointers):

class Investment {                         // root class for a hierarchy
public:                                    // of investment types
  bool isTaxFree() const;
  ...
};

Investment* createInvestment();       // factory function

std::tr1::shared_ptr<Investment>      // have tr1::shared_ptr
  pi1(createInvestment());                         // manage a resource

bool taxable1 = !(pi1->isTaxFree());               // access resource
                                                   // via operator->
...
std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr
                                                   // manage a
                                                   // resource

bool taxable2 = !((*pi2).isTaxFree());             // access resource
                                                   // via operator*

隐式转换: (TIC P296 自动类型转换)

class FontHandle

{

public:

    int t;

};

 

void releaseFont( FontHandle f)

{

    cout<<"in releaseFont./n";

}

 

void changeFontSize( FontHandle f, int n)

{

    cout<<"in change Font Size/n";

}

 

class Font {                           // RAII class

public:

    explicit Font(FontHandle fh)         // acquire resource;

       : f(fh)                              // use pass-by-value, because the

    {}                                   // C API does

 

    ~Font() { releaseFont(f); }          // release resource

 

    operator FontHandle() const { return f; }        // implicit conversion function

 

private:

    FontHandle f;                        // the raw font resource

};

 

...

       FontHandle f;

    f.t  = 0;

    Font ff(f);

    changeFontSize( ff, k); //implicit conversion happened

 

隐式转换的不利的方面是隐式转换增加了错误的机会。例如,一个客户可能会在有意使用 Font 的地方意外地产生一个 FontHandle

Font f1(getFont());

...

FontHandle f2 = f1;                 // oops! meant to copy a Font
                                    // object, but instead implicitly
                                    // converted f1 into its underlying
                                    // FontHandle, then copied that

现在,程序有了一个被 Font 对象 f1 管理的 FontHandle,但是这个 FontHandle 也能通过直接使用 f2 来加以利用。这会构成隐患,例如,当 f1 被销毁,字体将被释放,f2 则成为一个指向已被释放内存的指针(野指针)。

 

总结:

API 经常需要访问裸资源,所以每一个 RAII 类都应该提供取得它所管理的资源的方法。

访问可以通过显式转换或者隐式转换进行。通常,显式转换更安全,而隐式转换对客户来说更方便。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值