C++条款 在资源管理类中提供对原始资源的访问 6/55

本文讨论了在资源管理类中提供对原始资源的访问方式,包括显式转换(如get函数)和隐式转换的利弊。虽然显式转换更安全,但隐式转换可能更方便。资源管理类的主要目的是确保资源正确释放,而在需要与原始资源交互的API配合使用时,应谨慎选择合适的访问方式。
摘要由CSDN通过智能技术生成

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

Provide access to raw resources in resource-managing classes

资源管理类很值得我们去使用,是我们对抗资源泄漏的堡垒。排除此等泄漏是良好设计系统的根本性质。

但尽管如此,仍有很多APIs直接指涉资源,所以除非你永不录用这样的APIs,否则只得绕过资源管理对象直接访问原始资源

举个例子,之前的条款导入一个观念:使用智能指针如auto_ptr或tr1::shared_ptr保存factory函数createInvestment的调用结果:
 

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

假设你希望以某个函数处理Investment对象,像这样:

int daysHeld(const Investment* pi);

你希望这么调用它:
 

int days = daysHeld(pInv);

却不通过编译,因为dayHeld需要的是Investment*指针,你传给它的却是个类型为tr1::shared_ptr<Investment>的对象。

这时候你需要一个函数可将RAII class对象(本例为tr1::shared_ptr)转换为其所内含之原始资源(本例为底部之Investment*)。有两个做法可以达成目标:显示转换隐式转换。

tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件):

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

就像所有智能指针一样,tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),它们允许隐式转换至底部原始指针:

class Investment{
public:
    bool isTaxFree() const;
    ...
};
Investment* createInvestment();        //factory函数

std::tr1::shared_ptr<Investment>       //令tr1::shared_ptr管理一笔资源
    pi1(createInvestment());

bool taxablel = !(pi1->isTaxFree());   //经由operator->访问资源
...
std::auto<Investment>pi2(createInvestment());    //令auto_ptr管理一笔资源

bool taxable2 = !((*pi2).isTaxFree());    //经由operator*访问资源

由于有时候还是必须取得RAII对象内的原始资源,某些RAII class设计者于是提供一个转换函数。考虑下面这个用于字体的RAII class:

FontHandle getFont();        //这是个C API。为求简化暂略参数
void relaseFont(FontHandle fh);    //来自同一组C API

class Font{        //RAII class
public:
    explicit Font(FontHandle fh)    //获得资源
        :f(fh)                      //采用pass-by-value
    {}
    ~Font() { releaseFont(f );}
private:
    FontHandle f;                   //原始字体资源
}

假设有大量与字体相关的C API,它们处理的是FontHandles,那么“将Font对象转换为FontHandle”会是一种很频繁的需求。Font class可为提供一个显式转换函数,想get那样:

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

不幸的是这使得客户每当想要使用API时就必须调用get:
 

void changeFontSize(FontHandle f, int newSize);        //C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);       //明白地将Font转换为FontHandle

某些程序员可能会认为,如此这般地到处要求显式转换,足以使人们烦躁,不再愿意使用这个class,从而增加了泄漏字体的可能性,而Font class的主要设计目的就是为了防止资源泄漏。

另一个办法是令Font提供隐式转换函数,转型为FontHandle:
 

class Font{
public:
    ...
    operator FontHandle() const    //隐式转换函数
    { return f; }
    ...
}
/*
隐式转换函数:
    operator 类型名() 
        {
               实现转换的语句
        }
  1.在函数名前面不能指定函数类型,函数没有参数。
  2.其返回值的类型是由函数名中指定的类型名来确定的。
  3.类型转换函数只能作为成员函数,因为转换的主体是本类的对象,不能作为友元函数或普通函数。
  4.从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名。
*/

这使得客户调用C API时比较轻松且自然:

Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize);        //将Font隐式转换为FontHandle

但是这个隐式转换会增加错误发生机会。例如客户可能会在需要Font时意外创建一个FontHandle:
 

Font f1(getFont());
...
FontHandle f2 = f1;        //原意是要拷贝一个Font对象,却反而
                           //将f1隐式转换为其底部的FontHand然后才复制它

以上程序有个FontHandle由Font对象f1管理,但那个FontHandle也可通过直接使用f2取得。那几乎不会有好下场。例如当f1被销毁,字体被释放,而f2因此成为“虚吊的”。

是否该提供一个显式转换函数(例如get成员函数)将RAII class转换为其底部资源,或是应该提供隐式转换,答案取决于RAII class被设计执行的特定工作,以及它被使用的情况。通常显式转换函数如get是比较受欢迎的路子,因为它将“非故意之类型转换”的可能性最小化。然而有时候,隐式类型转换所带来的“自然用法”也会引发天秤倾斜。

你的内心也可能认为,RAII class内的那个返回原始资源的函数,与“封装”发生矛盾。那是真的,但一般而言它谈不上是什么设计灾难。RAII class并不是为了封装某物而存在;它们的存在是为了确保一个特殊行为——资源释放——会发生。

总结:

  • APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理的资源”的办法
  • 对原始资源的访问可能经由显式转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便

编于 03/28/2019 21:15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值