16.在资源管理类中提供对原始资源的访问

前言

</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
9
class Widget {
public:
explict Widget(WidegtHandle wh):w(wh) {}
~Widget() {release(w);}
operator WidegtHandle() const {return w;}//隐式转换
...
private:
WidgetHandle w;
}

如此一来,在API中直接可以将RAII对象作为参数传入。
这样有时候会发生崩盘,比如原意是拷贝一个RAII对象,但实际上却(发生隐式转换)拷贝了资源。


转换接口的合理性

</br>
是否应该提供转换,以及哪一种转换取决于你是否需要转换或者转换的频率。要记住一切操作都是为了令接口更加灵活且不易被误用。
你可能会认为转换接口与封装发生了矛盾。但事实并非如此,RAII对象负责资源管理,而非起封装数据的作用。作为类的设计者,我们有责任隐藏用户不需要了解的内容,同时提供给用户一切他们需要的东西。


总结

  1. APIs往往要求访问原始资源,所以每一个RAII class应该提供一个获取原始资源的函数。
  2. 显式转换比较安全,但隐式转换对客户比较方便,当然也更容易出错。