在资源管理类中提供对原始资源的访问
- 资源管理类很好,它们是对抗资源泄露的堡垒。
- 但许多 APIs 直接指涉资源,只得资源管理对象直接访问原始资源。
举个例子,条款 13 导入一个观念:使用智能指针如 auto_ptr 或 tr1::shared_ptr 保存 factory 函数如 createInvestment 的调用结果:
std::tr1::shared_ptr<Investment> pInv(createment()); // 见条款 13
假设你希望以某个函数处理 Investment 对象,像下面这样:
int daysHeld(const Investment* pi); // 返回投资天数
若你想要这样调用它:
int days = daysHeld(pInv); // 错误!
不能通过编译,因为 dayHeld 需要的是 Investment* 的指针,而 pInv 却是个类型为 tr1::shared_ptr< Investment >的对象。
这时候你需要一个可将 RAII class 对象(本例为 tr1::shared_ptr)转换为其所含之原始资源(本例为底部之 Investment*)的函数。有两种方法:显式和隐式转换。
① tr1::shared_ptr 和 auto_ptr 都提供一个 get 成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件):
int days = daysHeld(pInv.get()); // 将 pInv 内的原始指针传给 dayHeld
② (几乎)所有智能指针一样,tr1::shared_ptr 和 auto_ptr 也重载了指针取值(pointer dereferencing)操作符(operator-> 和 operator*),它们允许隐式转换至底部指针:
class Investment { // investment 继承体系的根类
public:
bool isTaxFree() const; // 原始资源
...
};
Investment* createInvestment(); // factory 函数
std::tr1::shared_ptr<Investment> pi1(createInvestment()); // 令 tr1::shared_ptr 管理一笔资源
bool taxable1 = !(pi1->isTaxFree()); // 经由 operator-> 访问资源
...
std::auto_ptr<Investment> pi2(createInvestment()); // 令auto_ptr 管理一笔资源
bool taxable2 = !((*pi2).isTaxFree()); // 经由 operator* 访问资源
由于有时候还是必须取得 RAII 对象内的原始资源,做法是提供一个隐式转换函数。
考虑下面这个例子,用于字体的 RAII class(对 C API 而言字体是一种原生数据结构):
FontHandle getFont(); // 这是个 C API
void releaseFont(FontHandle fh); // 来自同一组 C API
class Font {
public:
explicit Font(FontHandle fh): f(fh) // 获得资源,采用值传递,因为 C API 这样做
{ }
~Font() { releaseFont(f); } // 释放资源
private:
FontHandle f; //原始字体资源
};
假设有大量与字体相关的 C API,它们处理的是 FontHandles,那么 “ 将 Font 对象转换为 FontHandle ” 会是一种很繁琐的需求。Font class 可为此提供一个显式转换函数,像 get 那样:
class Font {
public:
...
FontHandle get() const { return f; } // 显式转换函数,主要为了防止资源泄露
...
};
不幸的是这使得用户每当想要使用 API 时就必须调用 get :
void changeFontSize(FontHandle f, int newSize); // C API
Font f(getFont());
int newSize;
...
changeFontSize(f.get(), newFontSize); // 明白地将 Font 转换为 FontHandle
另一个办法是令 Font 提供隐式转换函数,转型为 FontHandle:
class Font {
public:
...
operator FontHandle() const // 隐式转换函数
{ return f; }
...
};
这使得用户调用 C API 时轻松而自然:
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // 将 Font 隐式转换为 FontHandle
但这个隐式转换会增加错误发生的机会。例如用户可能会在需要 Font 时意外创建一个 FontHandle:
Font f1(getFont());
...
FontHandle f2 = f1; // 啊哦,原意是要拷贝一个 Font 对象
// 却反而将 f1 隐式转换为其底部的 FontHandle,然后才复制它
两种方法各有优缺点,最佳的设计是:让接口容易被正确使用,不易被误用
请记住:
- APIs 往往要求访问原始资源,所以每一个 RAII classes 应该提供一个 “ 取得其所管理资源 ” 的方法。
- 对原始资源的访问可能经由显式或隐式转换。一般而言显式转换比较安全,但隐式转换对用户比较方便。