简介
- API 往往要求会要求访问原始资源 (raw resources),所以没一个 RAII 类都需要提供一个可以让使用者访问原始资源的方法
- 对原始资源的访问可以通过显式或隐式转换获得。通常来说,显示转换比较安全,而隐式转换对使用者而言比较方便。
引子
虽然资源管理类(resource-managing classes)可以很好地为我们管理资源,并且让我们免于担心资源泄漏的问题。但不可否认的是,在实际进行程序设计时,有大量的 API 都需要直接操作,除非我们永远不用这些 API,否则我们不得不绕过资源管理对象来直接访问资源。
例如,在条款 13 中,我们推荐使用智能指针(例如 auto_ptr
、trl::shared_ptr
以及 C++11 之后的 std::unique_ptr
和 std::shared_ptr
)来保存工厂函数例如 createInvestment
的 调用结果,如下所示:
1 | std::shared_ptr<Investment> pInv(createInvestment()); // 见条款 13 |
但我们现在现在需要使用以下函数来处理 Investment
对象,如下:
1 | int daysHeld(const Investment* pi); // 返回投资天数 |
如果我们想直接将 pInv
传给 daysHeld
作为参数,程序会编译失败并报如下错误(完整代码见 ex1):
1 | int days = daysHeld(pInv); |
1 | main.cpp:16:25: error: cannot convert ‘std::shared_ptr<Investment>’ to ‘Investment*’ |
可以发现,智能指针并不能直接转换成裸指针(反之亦然),因此我们需要一些方法来将 RAII class 对象转换为其所管理的原始资源。
将资源管理类转换为其所管理的原始资源
通过显式转换访问原始资源
通常智能指针都会提供一个 get()
成员函数来执行显式转换,即返回智能指针的内部的原始指针,用法如下:
1 | int days = daysHeld(pInv.get()); |
通过隐式转换访问原始资源
这里的隐式转换包括两种含义:
- 像使用原始指针一样使用智能指针
例如 std::shared_ptr
和 std::unique_ptr
都重载了指针取值(pointer dereference)操作符,例如 operator->
和 operator*
,使用这两个操作符时会将智能指针隐式转换为原始指针,使得我们可以像使用原始指针一样使用智能指针,如下所示:
1 | class Investment { // investment 继承体系的根类 |
- 直接访问原始指针
有时候我们还是会需要直接使用原始指针,考虑以下例子:这是有关于字体的 RAII 类(在 C API 中,字体是一种原生数据结构):
1 | // C API to get a font handle |
考虑到我们在使用 C API 的时候会有大量的函数需要直接处理 FontHandle
,因此我们需要频繁的将 RAII 类 Font
的对象转换为 FontHandle
:例如改变字体 changeFontSize()
等等。其中一种做法是我们可以提供一个 get()
函数显式返回原始资源:
1 | class Font { |
但这需要在每一次调用 C API 时都需要调用这个函数,这很大程度上反而会打击程序员使用 Font
的积极性,因此可能会起反效果。因此,我们还可以令 Font
提供隐式转换函数转换为 FontHandle
,如下所示:
1 | class Font { |
这样在调用 C API 时可以很自然地使用 Font
,显式转换和隐式转换的用法如下:(完整代码见 ex2)
1 | Font f(getFont()); |
添加输出后运行结果如下:
1 | Got a font handle |
当然使用隐式转换的缺点也很显然,假如我们错误地使用 FontHandle
而非 Font
,如下所示:
1 | FontHandle f1 = f; |
这时候我们意外地获取 f
的原始资源(通过隐式转换),当 f
被销毁时,f1
会变成悬空的(dangle),本来我们不应该让这种代码编译通过,但由于我们提供了隐式转换函数因此可以通过。这是我们提供隐式转换函数时需要注意的。
显式和隐式转换的对比
在我们设计 RAII 类的时候,提供显式转换函数或者隐式转换函数需要视情况而定。大致情况下显式转换函数 get()
由于其安全性比较受欢迎,但是隐式转换函数带来使用的便利也有其优点。
结论
- API 往往要求会要吃访问原始资源 (raw resources),所以没一个 RAII 类都需要提供一个可以让使用者访问原始资源的方法
对原始资源的访问可以通过显式或隐式转换获得。通常来说,显示转换比较安全,而隐式转换对使用者而言比较方便。
完整可运行代码地址:Effective-Cpp-Reading-Note
- Efftive C++ 阅读笔记:Effective C++