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


    章节回顾:

    《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-读书笔记


    内存只是你必须管理的众多资源之一。其他常见的资源还包括文件描述器(file descriptors)、互斥锁(mutex locks)、图形界面中的字型和笔刷、以及网络sockets。无论哪一种资源,重要的是当你不再使用它时,将它还给系统。

    条款13:以对象管理资源

    下面有一个Investment继承体系:

    class Investment { ... };

    Investment是root class,获得Investment体系的对象是通过工厂函数:

    Investment* createInvestment(); //返回指针指向Investment继承体系动态分配对象

    显然,createInvestment的调用者使用后,有责任删除返回的指针,以释放对象。假设函数f()负责这个行为:

    void f()
    {
        Investment *pInv = createInvestment();
    ...
        delete pInv;
    }

    但至少存在几种情况使得f()无法删除createInvestment对象:

    (1)…区域有一个过早的return语句,导致delete没执行。

    (2)…区域抛出异常,控制流不会转向delete。

    当delete时,我们泄露的不只是内含Investment对象的那块内存,还包括其所保存的任何资源。

    为确保createInvestment()返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开f()时,该对象的析构函数会自动释放那些资源。标准库提供的auto_ptr是个“类指针对象”,即智能指针,其析构函数自动对其所指对象调用delete。

    下面是f()的修正版:

    void f()
    {
        std::auto_ptr<Investment> pInv(createInvestment());            //由auto_ptr的析构函数自动删除pInv
        ...
    }

    f()说明了以对象管理资源的两个重要方面:

    (1)获得资源后立刻放进管理对象内。

    以资源管理对象的观念常被称为“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization, RAII)。每一笔资源在获得的同时立刻被放进管理对象中。

    (2)管理对象运用析构函数确保资源被释放。

    无论控制流如何离开f(),一旦对象离开作用域(对象被销毁),其析构函数被调用,资源被释放。

    注意:由于auto_ptr被销毁时会自动删除它所指之物,所以别让多个auto_ptr同时指向某一对象。为了预防这个问题auto_ptr有一个很好的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一权。

    std::auto_ptr<Investment> pInv1(createInvestment());
    std::auto_ptr<Investment> pInv2(pInv1);            //pInv2指向对象,pInv1被设为null
    pInv1 = pInv2;                                    //pInv1指向对象,pInv2被设为null

    正是因为auto_ptr对象具备“非正常”的拷贝性质,所以不能用于STL容器的元素等。

    auto_ptr的替代方案是:引用计数型智能指针(reference-counting smart pointer (RCSP))。RCSP能够追踪共有多少对象指向某种资源,并在无人指向它时自动删除该资源。RCSP提供的行为类似垃圾回收,不同的是它无法打破环状引用,例如,两个其实已经没被使用的对象彼此互指,好像对象还处于“被使用”状态。

    TR1的tr1::shared_ptr就是一个RCSP,f()的新写法:

    void f()
    {
        std::tr1::shared_ptr<Investment> pInv(createInvestment());
    ...
    }

    f()的新版本与使用auto_ptr几乎相同,但拷贝行为正常了:

    void f()
    {
        ...
        std::tr1::shared_ptr<Investment> pInv1(createInvestment());
        std::tr1::shared_ptr<Investment> pInv2(pInv1);            //pInv1和pInv2指向同一对象
        pInv1 = pInv2;                                            //pInv1和pInv2指向同一对象
        ...
    }

    因为shared_ptr的复制行为是正常的,所以可用于STL容器以及其他auto_ptr非正常复制行为并不适用的情况。

    特别注意:auto_ptr和shared_ptr两者都在析构函数内做delete而不是delete[]。所以下面的操作是非常错误的:

    std::auto_ptr<std::string> aps(new std::string[10]);
    std::tr1::shared_ptr<int> spi(new int[1024]);

    之所以没有特别针对C++动态分配数组而设计的类似auto_ptr或tr1::shared_ptr是因为vector和string总是可以取代动态数组,这是你的第一选择。

    请记住:

    (1)为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

    (2)两个常被使用的RAII class分别是:tr1::shared_ptr和auto_ptr。前者是最好的选择,因为其copy行为比较正常。auto_ptr的copy会使被复制物指向null。


    条款14:在资源管理类中小心copying行为

    并非所有资源都是heap-based的,对这种资源auto_ptr和tr1::shared_ptr这样的智能指针往往不适用。举例如下:

    有两个函数用来处理Mutex互斥器对象:

    void lock(Mutex *pm);            //锁定pm指向的互斥器
    void unlock(Mutex *pm);            //解锁

    为确保不会忘记解锁,可以建立一个class管理锁,这个class基本结构由RAII支配,即“资源在构造期间获得,在析构期间释放”。

    class Lock
    {
    public:
        explicit Lock(Mutex *pm) : mutexPtr(pm)
        {
            lock(mutexPtr);
        }
        ~Lock()
        {
            unlock(mutexPtr);
        }
    private: 
        Mutex *mutexPtr;
    };

    客户是这样使用的:

    Mutex m;                    //定义所需的互斥器
    {
    ...
        Lock ml(&m);           //锁定互斥器
    ...
    }                            //自动解锁

    这样使用是可以的。但如果发生copy的情况呢?即当一个RAII对象被复制,会发生什么事情?

    Lock ml1(&m);
    Lock ml2(ml1);

    有四种可能:

    (1)禁止复制。

    许多时候允许RAII对象被复制并不合理。

    (2)对底层资源使用“引用计数法”。

    有时候我们希望保存资源,直到它的最后一个使用者被销毁。tr1::shared_ptr便是如此处理。

    注意:tr1::shared_ptr的缺省行为是当引用次数为0时删除其所指物,我们想要的是释放锁,而不是删除。我们可以指定“删除器”,这是一个函数,当引用次数为0时会被调用。auto_ptr无此性质,它总是删除它的指针。

    更改后的版本是:

    class Lock
    {
    public:
        explicit Lock(Mutex *pm) : mutexPtr(pm, unlock)        //unlock为删除器
        {
            lock(mutexPtr.get());
        }
    private: 
        std::tr1::shared_ptr<Mutex> mutexPtr;            //使用shared_ptr替换raw_pointer
    };

    注意:这个lock class不需要析构函数了,因为编译器会自动调用mutexPtr的析构函数,当引用计数为0时,自动调用删除器。

    (3)复制底部资源

    可以针对一份资源拥有任意数量的副本。这是的复制是深拷贝。

    (4)转移底部资源的拥有权

    你可能希望永远只有一个RAII对象指向一个raw recource,即使RAII对象被复制。这是资源的拥有权会从被复制物转移到目标物。auto_ptr便是如此。

    注意:copying函数(包括copy构造函数和copy assignment操作符)有可能被编译器创造出来,因此除非编译器版本做了你想做的事,否则你要自己编写它们。

    请记住:

    (1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。

    (2)普遍常见的RAII class copying行为是:禁止copying、使用引用计数。其他行为也可能被实现。

  • 相关阅读:
    (转)Java并发包:AtomicBoolean和AtomicReference
    (转)maven怎么 引入(或引用/使用) 自定义(或本地/第三方) jar的三种方式 图文教程 方法二最简单
    servlet3.0 异步处理
    (转)Groovy简介
    (转)springboot应用启动原理(一) 将启动脚本嵌入jar
    (转)springboot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载
    (转)运行jar应用程序引用其他jar包的四种方法 -- ClassLoader应用
    (转)二层网络结构和三层网络结构的对比
    Java语法糖4:内部类
    Java语法糖3:泛型
  • 原文地址:https://www.cnblogs.com/mengwang024/p/4446352.html
Copyright © 2020-2023  润新知