• 《Effective C++》第3章 资源管理(2)-读书笔记


    章节回顾:

    《Effective C++》第1章 让自己习惯C++-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    《Effective C++》第3章 资源管理(1)-读书笔记

    《Effective C++》第3章 资源管理(2)-读书笔记

    《Effective C++》第4章 设计与声明(1)-读书笔记

    《Effective C++》第4章 设计与声明(2)-读书笔记

    《Effective C++》第5章 实现-读书笔记

    《Effective C++》第8章 定制new和delete-读书笔记


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

    许多API直接指涉资源,所以除非你永远不用它们,否则都会绕过资源管理对象直接访问原始资源。假设使用tr1::shared_ptr管理对象。

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

    函数daysHeld的声明是这样的:

    int daysHeld(const Investment *pi);

    下面这种调用方式,肯定是错误的:

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

    因为函数需要的是指针,你传递是一个tr1::shared_ptr<Investment>对象。所以你需要一个函数将RAII对象转换为所内含的原始资源。有两种方法:隐式转换和显示转换。

    (1)显示转换

    tr1::shared_ptr和auto_ptr都提供了一个成员函数get返回内部的原始指针,这是显式转换。

    int days = daysHeld(pInv.get());    //好的,没有问题

    (2)隐式转换

    tr1::shared_ptr和auto_ptr都重载了操作符operator->和operator*,这样就允许隐式转换到原始指针。举例:假设Investment类有个成员函数bool isTaxFree() const;那么下面的调用是OK的:

    bool taxable1 = !(pInv->isTaxFree());        //好的,没有问题
    bool taxable2 = !((*pInv).isTaxFree());        //好的,没有问题

    现在的问题是,需要原始指针的地方(例如,函数形参),如何以智能指针代替。解决方法是:提供一个隐式转换函数。下面举个字体类的例子:

    FontHandle getFont();                    //取得字体句柄
    void releaseFont(FontHandle fh);        //释放句柄
    class Font
    {
    public:
        explicit Font(FontHandle fh) : f(fh){}
        ~Font()
        {
            releaseFont(f);
        }
    private:
        FontHandle f;
    };

    如果C API处理的是FontHandle而不是Font对象,当然你可以像tr1::shared_ptr和auto_ptr那样提供一个get()函数:

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

    这样是可以的,但客户还是觉得麻烦,这时候定义一个隐式转换函数是必须的。

    class Font
    {
    public:
        ...
        operator FontHandle() const { return f; }
        ...
    };

    注意:假设你已经知道了隐式转换函数的用法。例如:必须定义为成员函数,不允许转换为数组和函数类型等。

    完成了以上工作,对于下面这个函数的调用是OK的:

    void changeFontSize(FontHandle f, int newSize);
    Font f(getFont());
    int newFontSize;
    changeFontSize(f, newFontSize);        //好的,Font隐式转换为FontHandle了。

    隐式类型转换也增加了一种风险。例如有以下代码:

    Font f1(getFont());
    FontHandle f2 = f1;        //将Font错写成FontHandle了,编译仍然通过。

    f1被隐式转换为FontHandle,这时f1和f2共同管理某个资源,f1被销毁,字体释放,这时候你可以想象f2的状态(原谅我这个词我不会说),再销毁f2,必然会造成运行错误。通常提供一个显示转换get函数是比较好的,因为它可以避免非故意的类型转换的错误,这种错误估计会耗费你很长的调试时间(我遇到过的情况)。

    请记住:

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

    (2)对原始资源的访问可以通过显式转换或隐式转换。一般显式转换比较安全,但隐式转换对客户比较方便。


    条款16:成对使用new和delete时要采取相同形式

    我相信你肯定一眼看出以下代码的问题:

    std::string *stringArray = new std::string[100];
    delete stringArray;

    程序行为是未定义的。stringArray所含的100个string对象,99个可能没被删除,因为它们的析构函数没被调用。

    delete必须要知道的是:删除的内存有多少个对象,决定了调用多少个析构函数。

    单一对象和数组的内存布局肯定是不同的,数组占用的内存也许包含一个“数组大小”的记录。(编译器干的事)你可以告诉编译器删除的是数组还是单一对象:

    std::string *stringPtr1 = new std::string;
    std::string *stringPtr2 = new std::string[100];
    delete stringPtr1;        //删除一个对象
    delete [] stringPtr2;    //删除一个数组

    这个规则很简单,但有一点需要注意:

    typedef std::string AddressLines[4];
    std::string *pal = new AddressLines;
    
    delete pal;                //不好,行为未定义。
    delete [] pal;            //很好。

    所以,最好不要对数组做typedef。

    请记住:如果new表达式中使用了[],必须在相应的delete表达式中使用[];如果new表达式中不使用[],一定不要在相应的delete表达式中使用[]。


    条款17:以独立语句将newed对象置入智能指针

    假设有以下函数,具体含义我们可以先忽略:

    int priority();
    void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

    先说明一点的是,当你以下面形式调用processWidget时,肯定是错误的:

    processWidget(new Widget, priority());

    编译出错。tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是explicit,即禁止隐式类型转换的。所以,正常情况你应该这样调用:

    processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());    //好的,没有问题

    以这种调用方式,执行函数体之前,有三个工作要做:

    (1)调用priority。

    (2)执行"new Widget"。

    (3)调用tr1::shared_ptr构造函数

    可以肯定的是(2)在(3)前面执行,但(1)的执行次序不能确定。假设(1)在第二个被执行,则如果调用priority出现异常,new Widget返回的指针将会遗失,因为还未执行tr1::shared_ptr的构造函数。所以,发生了资源泄露。

    避免这类问题很简单:使用分离语句。

    std::tr1::shared_ptr<Widget> pw(new Widget);
    processWidget(pw, priority());                //绝不会造成泄露

    请记住:以独立语句将newd对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能造成难以察觉的资源泄露。

  • 相关阅读:
    一种想法
    识别link_text
    识别name
    识别id
    文件的读写
    条件和循环
    网站测试-功能测试小结
    拷贝
    #团队博客作业1-小组成员介绍
    软件测试基础-Homework1
  • 原文地址:https://www.cnblogs.com/mengwang024/p/4447761.html
Copyright © 2020-2023  润新知