View Code
由于 某些时刻你需要获取一个 RAII 对象中的原始资源,所以一些 RAII 类的设计者使用了一个小手段来使系统正常运行,那就是:提供一个隐式转换函数。举例说,以下是一个 C 语言 API 中提供的处理字体的一个 RAII 类:
FontHandle getFont(); // 来自一个 C 语言 API 省略参数表以简化代码
void releaseFont(FontHandle fh); // 来自同一个 C 语言 API
class Font // RAII 类
{
public:
explicit Font(FontHandle fh): f(fh) // 通过传值获取资源 因为这一 C 语言 API 这样做
{
}
~Font() { releaseFont(f); } // 释放资源
private:
FontHandle f; // 原始的字体资源
};
假设这里有一个大型的相关的 C 语言 API 仅仅通过 FontHandle 解决字体问题,那么将存在十分频繁的把 Font 对象转换为 FontHandle 的操作。
Font 类可以提供一个显式转换函数,比如 get :
class Font
{
public:
FontHandle get() const { return f; }
// 进行显式转换的函数
public:
explicit Font(FontHandle fh): f(fh) // 通过传值获取资源 因为这一 C 语言 API 这样做
{
}
~Font() { releaseFont(f); } // 释放资源
private:
FontHandle f; // 原始的字体资源
};
遗憾的是,这样做使得客户端程序员在每次 与这一 API 通 信时都要调用一次 get :
void changeFontSize(FontHandle f, int newSize); // 来自一个 C 语言 API
Font f(getFont());
int newFontSize;
changeFontSize(f.get(), newFontSize);
// 显式转换:从 Font 到 FontHandle
由于需要显式请求这样的转换,这样做显得得不偿失,一些程序员也许会拒绝使用这个类。然而这又增加了字体资源泄漏的可能性,这与 Font 类的设计初衷是完全相悖的。
有一个替代方案,让 Font 提供一个将其隐式转换为 Fonthandle 的函数:
class Font
{
public:
operator FontHandle() const { return f; } // 进行隐式转换的函数 规则是 operator T(T为类型 )
public:
explicit Font(FontHandle fh): f(fh) // 通过传值获取资源 因为这一 C 语言 API 这样做
{
}
~Font() { releaseFont(f); } // 释放资源
private:
FontHandle f; // 原始的字体资源
};
这使得调用这一 C 语言 API 的工作变得简洁而且自然:
void changeFontSize(FontHandle f, int newSize); // 来自一个 C 语言 API
Font f(getFont());
int newFontSize;
changeFontSize(f, newFontSize); // 隐式转换:从 Font 到 FontHandle
隐式转换会带来一定的负面效应:它会增加出错的可能。比如说,一个客户端程序员在一个需要 Font 的地方意外地创建了一个 FontHandle :
Font f1(getFont());
...
FontHandle f2 = f1; // 啊哦!本想复制一个 Font 对象,
// 但是却却将 f1 隐式转换为其原始的FontHandle ,然后复制它
现在程序中有一个 FontHandle 资源正在由 Font 对象 f1 来管理,但是仍然可以通过 f2 直接访问 FontHandle 资源。这是很糟糕的。比如说,当 f1 被销毁时,字体就会被释放, f2 也一样。
是 为 RAII 类提供显式转换为其原始资源的方法,还是允许隐式转换,上面两个问题的答案取决于 RAII 类设计用于完成的具体任务,及其被使用的具体环境。
最好的设计方案应该遵循第 18 条 的建议,让接口更容易被正确使用,而不易被误用。