条款15:在资源管理类中提供对原始资源的访问

127 篇文章 7 订阅
39 篇文章 3 订阅

条款15:在资源管理类中提供对原始资源的访问

Provide access to raw resources in resource-managing classes.

显式转换与隐式转换

所谓资源管理类(resource-managing classes),可以有效的帮助我们去预防资源泄漏
但是,虽然在理想的情况下,我们希望所有的资源与对象之间的互动都依赖于这样的资源管理类,仍有许多的API会直接去涉及原始资源(raw resource)。

举个例子,在前面的条款13中,我们可以得知:使用智能指针auto_ptr或者tr1::shared_ptr来保存factory函数例如createInvestment的调用结果:

std::tr1::shared_ptr<Investment> pInv(createInvestment());

如果我们希望用某个函数来处理Investment对象:

int dayHeld(const Investment* pi);      //返回投资的天数

此时,如果我们想要这样去调用:

int days = daysHeld(pInv);      //错误!!

这样做是错误的,因为daysHeld需要的是Investment*的指针,而并非此时传递给它的类型为tr1::shared_ptr的对象。

因此,此时需要一个函数,可以将RAII class对象转换为其所内含的原始资源
(即tr1::shared_ptr -> Investment*)
有两种方法可以实现这样的功能:

  • 显式转换
  • 隐式转换

首先说显式转换,tr1::shared_ptr和auto_ptr都提供了一个get成员函数,用来执行显式转换。也就是说,可以返回智能指针内部的原始资源(的复件):

int days = daysHeld(pInv.get());    //成功的将pInv内的原始指针传给了daysHeld

就像几乎所有的智能指针一样,这些对类指针对象都重载了指针取值(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 {    //RAII class
public:
    explicit Font(FontHandle fh) : f(fh)   //获得资源,采用pass-by-value的方法
    { }
    ~Font() { releaseFont(f); }    //释放资源
private:
    FontHandle f;     //原始(raw)字体资源
};

假设有大量与字体相关的C API,它们处理的都是FontHandle,那么“将Font对象转换为FontHandle”将会是一件非常繁琐的事情。因此,Font class可以提供一个显式的转换函数,就像上面的get一样:

class Font {
public:
    ...
    FontHandle get() const { return f; }    //显式转换函数
    ...
};

然而,每次用户想要使用API时,都必须要调用get:

void changeFontSize(FontHandle f, int newFontSize); //C API
Font f(getFont());
int newFontSize;
...
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对象,但是因为隐式转换的原因(f2前面的FontHandle),将f1给隐式转换为了FontHandle,然后才执行了复制操作

在上面的程序中,FontHandle由Font对象f1进行管理,但是这个FontHandle也可以直接通过f2进行取得。这样就会引发问题,例如当f1被销毁时,字体会被释放,而f2因此会成为“虚吊的”(dangle)。

综上所说,是否应该提供一个显式转换函数(例如get成员函数)将RAII class转换为其底部资源,还是提供隐式转换,取决于其执行的具体功能,具体说来:

  • 让接口容易被正确使用,不易被误用。

因此,通常显式转换函数如get就是比较好的方法,因为:

  • 显式转换将非故意的类型转换的发生的可能性最小化了。

关于RAII class,它们并不是为了封装,而是为了确保:资源释放,这一行为,一定会发生。
最后:

1,APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理的资源”的办法。

2,对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对用户而言更为方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值