您的位置:首页 > 编程语言 > C语言/C++

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

2018-01-20 16:43 288 查看
转:http://blog.csdn.net/zs634134578/article/details/18896883

问题聚焦:

    资源管理类是为了对抗资源泄露。

    如果一些函数需要访问原始资源,资源管理类应该怎么做呢?

    

关于资源管理的概念总是显得那么的高大上,其实只是抽象一点。

下面用一个例子还说明本节的主题:

在前面 Effective C++(13) 用对象管理资源 中我们提到过,使用智能指针保存工厂函数返回的结果

[cpp] view
plain copy

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

  

//加入有某个函数,用来处理Investment对象,像下面这样  

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

  

//那么像下面这样的调用时错误的  

int days = daysHeld(pInv);  

错误原因很明显,daysHeld需要的是Investment* 指针,而传给它的却是类型为tr1::shared_ptr<Investment>的对象。

这时候,需要的是一个类型转换,将tr1::shared_ptr<Investment>类型的指针转换为Investment*类型的指针。

有两个做法可以达到这个目标:显示转换和隐式转换。

显示转换

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

[cpp] view
plain copy

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

隐式转换

几乎所有的智能指针都重载了指针取值操作符(operator->和operator*),

它们允许将当前智能指针隐身转换为它管理的资源的指针。

[cpp] view
plain copy

class Investment   

{  

public:  

    bool isTaxFree() const;      // 被管理资源内部定义的一个方法  

    ... ...   

};  

  

/*使用*/  

Investment* createInvestment();  

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

bool taxable = !(pi1->isTaxFree());  

......  

std::auto_ptr<Investment> pi2(createInvestment());  

bool taxable = !((*pi2).isTaxFree());  

......  

如果让你设计一个RAII class,可能会像下面这个管理字体资源的例子中那样,提供一个显式转换或者隐式转换。

我喜欢贴上一大段的代码,说再多理论不如一段代码,所以不理解的地方请注意上下的注释。

[cpp] view
plain copy

// 类设定  

FontHandle getFont();    // 获取字体资源函数  

void releaseFont(FontHandle fh);    // 释放字体资源  

  

class Font   

{  

public:  

    explicit Font(FontHandle fh)  

        : f(fh)  

    { }  

    ~Font()   

    {  

        releaseFont(f);      

    }  

private:  

    FontHandle f;  

};  

  

/*使用该类*/  

// 假如有大量与字体相关的C API,它们处理的是FontHandles,那么“将Font对象转换为FontHandle”会是一种很频繁的需求。  

  

// 提供一个显式转换  

class Font   

{  

public:  

    ... ...   

    FontHandle get() const { return f; }  // 显示转换函数  

    ... ...  

};  

  

// 使用该显式转换  

void changeFontSize(FontHandle f, int newSize);   // C API(系统给定)  

  

Font f(getFont());  

int newFontSIze;  

... ...  

changeFontSize(f.get(), newFontSize);  

  

// 频繁地调用这个get方法会让人容易厌烦  

// 下面提供一个隐式转换  

class Font  

{  

public:  

    ...  

    operator FontHandle() const { return f; }  

    ...  

};  

  

//使用该隐式转换  

Font f(getFont());  

int newFontSize;  

...  

changeFontSize(f, newFontSize);  

  

// 但是这个隐式转换会增加错误发生机会  

Font f1(getFont());  

...  

Font f2 = f1;        // 本意死打算拷贝一个Font对象,结果却将f1隐式转换为底部的FontHandle才复制,编译不通过  

FontHandle f3 = f1;        // 现在类型匹配没问题了吧,但是当f1被销毁,其内部的资源也同时被销毁,那么这个f3指向的资源也同时被释放。  

看了上面这个例子可能你比较迷惑,你可能会问,到底是用显式转换还是隐式转换啊?

其实,答案主要取决于特定的场景和使用情况。

最佳的设计是要遵守一个原则:让接口容易正确使用,不易被误用。

敏感的同学可能注意到,返回原始资源的这个用法,和“封装”这个目的发生了矛盾。

这里,我们需要明确一点的是,资源管理类的存在不是为了封装某物而存在的,它们的存在是为了确保一个特殊行为——资源释放,一定会发生。

设计良好的classes,是隐藏了客户不care的部分,而备妥客户需要的所有东西。

小结:

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

参考资料:
《Effective C++ 3rd》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐