# Item 15: 在资源管理类中准备访问裸资源(raw resources)
作者:Scott Meyers
译者:fatalerror99 (iTePub's Nirvana)
发布:http://blog.csdn.net/fatalerror99/
资源管理类真是太棒了。他们是你防御资源泄漏的防波堤,没有这样的泄漏是设计良好的系统的基本特征。在一个完美的世界中,你可以在所有与资源的交互中依赖这样的类,从来不需要因为直接访问裸资源(raw resources)而玷污你的手。但是这个世界并不完美,很多 API 直接涉及资源,所以除非你计划坚决放弃使用这样的 API(这种事很少会成为实际),否则,你就要经常绕过资源管理类而直接处理裸资源(raw resources)。
例如,Item 13 介绍的使用类似 auto_ptr 或 tr1::shared_ptr 这样的智能指针来持有调用类似 createInvestment 这样的 factory 函数的结果:
```
std::tr1::shared_ptr<Investment> pInv(createInvestment()); // from Item 13
```
假设你打算使用的一个与 Investment 对象一起工作的函数是这样的:
```
int daysHeld(const Investment *pi); // return number of days
// investment has been held
```
你打算像这样调用它,
```
int days = daysHeld(pInv); // error!
```
但是这代码不能编译:daysHeld 要求一个裸的 Investment\* 指针,但是你传给它一个类型为 tr1::shared_ptr<Investment> 的对象。
你需要一个将 RAII 类(当前情况下是 tr1::shared_ptr)的对象转化为它所包含的裸资源(例如,底层的 Investment\*)的方法。有两个常规方法来做这件事。显式转换和隐式转换。
tr1::shared_ptr 和 auto_ptr 都提供一个 get 成员函数进行显示转换,也就是说,返回一个智能指针对象内部的裸指针(raw pointer)(或它的一个副本):
```
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*
...
```
因为有些时候有必要取得 RAII 对象内部的裸资源,所以一些 RAII 类的设计者就通过提供一个隐式转换函数来给刹车抹油。例如,考虑以下这个 RAII 类,它要为 C API 提供原始状态的字体资源:
```
FontHandle getFont(); // from C API—params omitted
// for simplicity
void releaseFont(FontHandle fh); // from the same C API
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
private:
FontHandle f; // the raw font resource
};
```
假设有一个巨大的与字体有关的 C API 只能与 FontHandle 打交道,这就需要频繁地将 Font 对象转换为 FontHandle。Font 类可以提供一个显式的转换函数,比如 get:
```
class Font {
public:
...
FontHandle get() const { return f; } // explicit conversion function
...
};
```
不幸的是,这就要求客户每次与 API 通信时都要调用 get:
```
void changeFontSize(FontHandle f, int newSize); // from the C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // explicitly convert
// Font to FontHandle
```
一些程序员可能发现对显式请求这个转换的需求足以令人郁闷而避免使用这个类。反过来,设计 Font 类又是为了预防泄漏字体资源的机会的增长。
可选择的办法是为 Font 提供一个隐式转换到它的 FontHandle 的转换函数:
```
class Font {
public:
...
operator FontHandle() const { return f; } // implicit conversion function
...
};
```
这样就可以使对 C API 的调用简单而自然:
```
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // implicitly convert Font
// to FontHandle
```
不利的方面是隐式转换增加了错误的机会。例如,一个客户可能会在有意使用 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 则被悬挂(dangle)。
关于是否提供从一个 RAII 类到它的底层资源的显式转换(例如,通过一个 get 成员函数)或者允许隐式转换的决定,要依靠 RAII 类被设计履行的具体任务和它被计划使用的细节而做出。最好的设计很可能就是坚持 Item 18 的建议(使接口易于正确使用,而难以错误使用)的那一个。通常,类似 get 的一个显式转换函数是更可取的方式,因为它将意外的类型转换的机会减到最少。偶尔的,通过隐式类型转换提高使用的自然性将使天平向那个方向倾斜。
你可能已经意识到,函数返回一个 RAII 类内部的裸资源破坏了封装。这是正确的,但这并非像它开始看上去那样是个设计的祸患。RAII 类的存在并非为了封装什么东西;它的存在是为了确保一个特殊的动作——资源释放——的发生。如果你希望,封装资源的地位也可以提高到这个主要功能之上,但这并非必需。此外,一些 RAII 类将实现的真正封装和底层资源的非常宽松的封装结合在一起。例如,tr1::shared_ptr 封装了它的引用计数的全部机制,但它依然提供对它所包含的资源的简单访问。就像大多数设计良好的类,它隐藏了客户不需要看到的,但它也让客户的确需要访问的那些东西可以利用。
Things to Remember
* API 经常需要访问裸资源,所以每一个 RAII 类都应该提供取得它所管理的资源的方法。
* 访问可以通过显式转换或者隐式转换进行。通常,显式转换更安全,而隐式转换对客户来说更方便。
- Preface(前言)
- Introduction(导言)
- Terminology(术语)
- Item 1: 将 C++ 视为 federation of languages(语言联合体)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 确保 objects(对象)在使用前被初始化
- Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
- Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
- Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
- Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
- Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
- Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中处理 assignment to self(自赋值)
- Item 12: 拷贝一个对象的所有组成部分
- Item 13: 使用对象管理资源
- Item 14: 谨慎考虑资源管理类的拷贝行为
- Item 15: 在资源管理类中准备访问裸资源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一个独立的语句中将 new 出来的对象存入智能指针
- Item 18: 使接口易于正确使用,而难以错误使用
- Item 19: 视类设计为类型设计
- Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
- Item 21: 当你必须返回一个对象时不要试图返回一个引用
- Item 22: 将数据成员声明为 private
- Item 23: 用非成员非友元函数取代成员函数
- Item 24: 当类型转换应该用于所有参数时,声明为非成员函数
- Item 25: 考虑支持不抛异常的 swap
- Item 26: 只要有可能就推迟变量定义
- Item 27: 将强制转型减到最少
- Item 28: 避免返回对象内部构件的“句柄”
- Item 29: 争取异常安全(exception-safe)的代码
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之间的编译依赖
- Item 32: 确保 public inheritance 模拟 "is-a"
- Item 33: 避免覆盖(hiding)“通过继承得到的名字”
- Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)
- Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法
- Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- Item 39: 谨慎使用 private inheritance(私有继承)
- Item 40: 谨慎使用 multiple inheritance(多继承)
- Item 41: 理解 implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)
- Item 42: 理解 typename 的两个含义
- Item 43: 了解如何访问 templatized base classes(模板化基类)中的名字
- Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码
- Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- Item 46: 需要 type conversions(类型转换)时在 templates(模板)内定义 non-member functions(非成员函数)
- Item 47: 为类型信息使用 traits classes(特征类)
- Item 48: 感受 template metaprogramming(模板元编程)
- Item 49: 了解 new-handler 的行为
- Item 50: 领会何时替换 new 和 delete 才有意义
- Item 51: 编写 new 和 delete 时要遵守惯例
- Item 52: 如果编写了 placement new,就要编写 placement delete
- 附录 A. 超越 Effective C++
- 附录 B. 第二和第三版之间的 Item 映射