条款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);
}
既置当前的_Myprt为NULL,并将其复制到新的实例中去
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_ptr。tr1::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 类都应该提供取得它所管理的资源的方法。
访问可以通过显式转换或者隐式转换进行。通常,显式转换更安全,而隐式转换对客户来说更方便。