Effective C++ 读书笔记 Item15 在资源管理类中提供对原始资源的访问

到现在我们一直在一个完美的世界里,资源管理类会帮你完成对资源的所有操作,自己不用关心资源管理类里面的原始资源。但现实是残酷的,有时我们依然需要直接接触资源管理类所封装的原始资源。

我们继续第13章的投资管理的栗子,我们在为不同类型的投资写函数库:

std::shared_ptr<Investment> pInv(createInvestment()); //某个Investment的智能指针对象

现有函数:

int daysHeld(const Investment* pi); //返回某个Investment对象的持有时间

如果你直接把智能指针对象传入上面的函数中:

int days = daysHeld(pInv);

这样编译器会报错,因为函数需要裸指针类型的参数,而你传入的是智能指针类型。你需要做的也很简单,把智能指针转换为裸指针,使用隐式转换或者显式转换

shared_ptr和auto_ptr都有一个成员函数get(),用来执行显式转换,返回智能指针对象所包含的裸指针:

int days = daysHeld(pInv.get());

shared_ptr和auto_ptr也重载了指针的解引用运算符,即->和*,这意味着我们可以通过它们来实现隐式转换:

class Investment{  //定义Investment继承层次的基类
  public:
    boos isTaxFree() const;
  ...
};

Investment* createInvestment(); //用来返回指向Investment对象的指针

std::shared_ptr<Investment> pi1(createInvestment()); //使用shared_ptr管理资源
bool taxable1 = !(pi1->isTaxFree());  //使用->操作符接触资源

std::auto_ptr<Investment> pi2(createInvestment());  //使用auto_ptr来管理资源
bool taxable2 = !((*pi2).isTaxFree());  //使用*操作符接触资源

有时我们需要把RAII资源管理对象所封装的原始资源拿出来,我们可以定义一个转换函数,将资源管理类隐式或显式转换为原始资源。例如要实现对C API中的字体类型(font)的资源管理:

FontHandle getFont(); //C API定义的分配字体函数
void releaseFont(FontHandle fh);  //C API定义的释放字体函数

现在我们定义自己的RAII资源管理类:

class Font{
  public:
    explicit Font(FontHandle fh): //C只能使用值传递
    f(fh) {} //构造时获取资源
    ~Font() {releaseFont(f);}  //析构时释放资源
    ...
  private:
    FontHandle f;
};

如果我们要使用某些C API只能使用FontHandle类型,我们就需要把Font类型显式转换为FontHandle类型,因此我们定义一个显式转换的函数get():

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

但是这样显式转换的缺点就是每次使用都要调用get()函数,比较麻烦:

void changeFontSize(FontHandle f, int newSize); //改变字体大小的C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);  //需要使用get()来显式转换

另外一个缺点就是,我们既然写了RAII资源管理类,为什么还要每次只使用它的原始资源,这不是跟我们希望避免资源泄漏的初衷背道而驰吗?我们下面来看看隐式转换:

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

这样调用API就会简单很多:

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

但是隐式转换也有缺点,某些类型错误就不会被编译器探测到了:

Font f1(getFont());
...
FontHandle f2=f1;

我们希望把一个Font对象拷贝进另一个Font对象,但比如某个人的辣鸡IDE把打进去的Font变成了FontHandle,这样就把我们的资源管理对象变成了原始资源对象,编译器也不会报错。

结果就是,程序有一个FontHandle对象被f1封装和管理,但这个FontHandle通过上面的操作被拷贝进了f2,这样f1和f2同时控制着一个FontHandle。如果f1被释放,这个FontHandle也将被释放,就会导致f2的字体被损坏。


至于具体使用隐式转换还是显式转换还是要根据不同的需要来决定,要保证代码的正确性,显式转换的get()函数可能是更好的选择,如果想要代码自然易懂,隐式转换可能更好,两者各有优缺点。

可能你会问,提取RAII资源管理类的原始资源会不会破坏资源管理类的封装?是的,但是资源管理类不是用来封装某些东西的,而是用来保证某些必要的操作被执行,例如释放资源,当然如果需要,封装也可以成为在这之上的另一种功能。资源管理类的典范,会把用户不需要的功能隐藏起来,给用户需要的东西留下简单易用的出入口,例如shared_ptr,把引用计数的机制完全封装起来,但用户会时不时需要使用裸指针,因此它提供了能直接返回裸指针的函数。

总结:

  • API通常需要使用原始资源作为参数,因此我们的RAII资源管理类要保证它所封装的资源是对外界可接触的。
  • 可以通过隐式转换或显式转换来实现对外接触。显式转换总体上更安全,隐式转换则对用户更加方便。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值