前言
</br>
资源管理类是排除资源泄漏的壁垒,是良好设计系统的根本性质。但有许多api直接指涉资源,有时不得不绕过资源管理对象直接访问原始资源。
问题实例
</br>
举例而言,假设有一个shared_ptr保存了factory函数生成的资源,1
shared_ptr<Investment> spInv(createInvestment());
但现在API如下:1
int daysHeld(const Investment* pi);
我们肯定没法把spInv传入进去,因为该函数需要的是Investment*指针,而不是shared_ptr<Investment>类型的对象。
解决方案
</br>
有两个做法可以把RAII对象转换为原始资源:显式转换和隐式转换。
显式转换
shared_ptr与auto_ptr都提供一个get成员函数,用来执行显式转换。该函数返回智能指针内部的原始指针(的副本)。我们可以写作:1
daysHeld(spInv.get());
另外,智能指针并非仅有显式转换,它们重载了解引用操作符(operator*,operator->),这两种操作符执行了隐式转换。(我以为是调用了get().opertaor*,inline之后调用成本也不是很高,不过生成的文件可能变大了)
隐式转换
对于需要频繁转换的RAII classes来说,不断调用get()未免繁琐,因此我们可以提供一个隐式转换操作符。举例而言:1
2
3
4
5
6
7
8
9class Widget {
public:
explict Widget(WidegtHandle wh):w(wh) {}
~Widget() {release(w);}
operator WidegtHandle() const {return w;}//隐式转换
...
private:
WidgetHandle w;
}
如此一来,在API中直接可以将RAII对象作为参数传入。
这样有时候会发生崩盘,比如原意是拷贝一个RAII对象,但实际上却(发生隐式转换)拷贝了资源。
转换接口的合理性
</br>
是否应该提供转换,以及哪一种转换取决于你是否需要转换或者转换的频率。要记住一切操作都是为了令接口更加灵活且不易被误用。
你可能会认为转换接口与封装发生了矛盾。但事实并非如此,RAII对象负责资源管理,而非起封装数据的作用。作为类的设计者,我们有责任隐藏用户不需要了解的内容,同时提供给用户一切他们需要的东西。
总结
- APIs往往要求访问原始资源,所以每一个RAII class应该提供一个获取原始资源的函数。
- 显式转换比较安全,但隐式转换对客户比较方便,当然也更容易出错。