Effective C++ 读书笔记(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40028201/article/details/89673264

Effective C++ 读书笔记(三)

3、 资源管理

条款 13:以对象管理资源

  1. 在一个作用域内,在delete 之前就return了,会造成内存泄漏,所以delete管理内存远远不够

    
    void fun()
    {
    	Investment* pInv=CreateInvestment();
    	……//这里提前 return
    	delete pInv;//释放资源
    }
    
    
  2. 用对象控制对象,离开了作用域自然会调用析构函数析构,比如使用智能指针auto_ptr(唯一资源使用权)这里

    1. RAII:资源获取即初始化(resource acquisition is initialization)。获取资源后立即放进对象内进行管理。
    2. 管理对象运用析构函数确保资源释放。管理对象是开辟在栈上面的,离开作用域系统会自动释放管理对象,自然会调用管理对象的析构函数。
    3. 还有一种指针是引用计数器型指针,会记录多少个对象在使用资源,计数器为0,就释放,如share_ptr这里
    4. auto_ptr和shared_ptr释放资源用的都是delete,而不是delete[],对于数组指针,shared_array来对应。类似的还有scope_array这里

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

  1. 但是并不是所有资源都是开辟在堆上,有时候我们需要自己建立资源管理类

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

    这样客户对Lock的使用方法符合RAII方式:

    
    Mutex m;//定义互斥器
    ……
    {//建立区块来定义critical section
    	Lock(&m);
    	……//执行critical section 内的操作
    }//在区块末尾,自动解除互斥器的锁
    
    

    当一个RAII对象被复制,会发生什么?有以下做法

    1. 禁止复制,将coping函数设置为私有,条款6

    2. 对管理资源使用引用计数法,复制的时候就加1 。mutexPrt变为类型从Mutex*变为shared即可

      
      class Lockprivate Uncopyable{
      public:
      	explicit Lock(Mutex* mu):mutexPtr(mu,unlock)//以某个Mutex初始化,unlock作为删除其
      	{
      		lock(mutexPtr);
      	}
      private:
      	shared_prt<Mutex> mutexPtr;
      };
      

      注意的是在这个类中并没有自己编写析构函数。因为mutexPtr是类中的普通成员变量,编译器会自动生成析构函数类析构这样的变量。这个在条款5中有说明。

    3. 复制底部资源

      使用资源管理类的目的是保证不需要这个资源时能正确释放。如果这种资源可以任意复制,我们只需编写好适当的copying函数即可。确保复制时是深度复制。关于深复制,参考这里
      C++中的string类,内部是指向heap的指针。当string复制时,底层的指针指向的内容都会多出一份拷贝

    4. 转移底层资源的拥有权。

      有时候资源的拥有权只能给一个对象,这时候当资源复制时,就需要剥夺原RAII类对该资源的拥有权。像auto_ptr。在C++11新标准中的std::move便是这个功能。可以把一个左值转换为一个右值左值与右值

    copying函数如果你不编写,编译器会帮你合成,其合成版本行为可参考条款5。要记住的是不论是自己编写还是编译器合成,都要符合自己资源管理类的需要。

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

  1. 原始资源,没有经过封装的指针(可以这样理解)

    //用智能指针来保存返回值
    shared_prt<Investment> pInv=(createInvestment());
    //有这样一个函数,显然是无法将只能指针对象的,这时就需要一个函数将管理的原始资源暴露出来
    int dayHeld(const Investment* pi);
    //shared_ptr和auto_ptr都提供一个get函数,用于执行这样的显示转换
    dayHeld(pInv.get());
    
  2. 为了使智能指针使用起来像普通指针一样,它们要重载指针取值(pointerdereferencing)操作符(operator->和operator*),它们允许转换至底部原始指针。

  3. RAII class内的返回资源的函数和封装资源之间有矛盾。的确是这样,但这样不是什么灾难。RAII class不是为了封装资源,而是为确保资源释放。

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

  1. 如果使用new开辟内存,就使用delete释放。如果使用new[]开辟内存,就使用delete[]释放。
  2. 尽量不使用对数组做typedef动作。在C++的STL中有string、vector等templates(条款54),可以将数组需求降至几乎为零

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

  1. 在使用智能指针时,应该用独立的语句把新创建的对象指针放入智能指针,否则可能会造成内存泄露

    //对于这个的传参
    int processWidget(shared_ptr<Widget> pw, int priority);
    
    在调用processWidget之前有三件事:
    
    1、执行priority()函数
    2、执行new Widget
    3、执行shared_ptr构造函数
    

    C++编译器会以什么样的次序来完成这些事情呢?弹性很大。在Java和C#中,总是以特定的次序来完成这样函数参数的计算,但在C++中却不一定。唯一可以确定的是new Widget在shared_ptr之前调用。但是函数priority排在第几却不一定。假设排在第二,那么顺序就是1、执行new Widget。2、执行函数priority()。3执行shared_ptr构造函数。

    如果对函数priority()调用出现异常,那么new Widget返回的指针还没来得及放入shared_ptr中。这样会造成内存泄露。

    因此可以分开写

    shared_prt<Widget> pw(new Widget);
    processWidget(pw,priority());
    

猜你喜欢

转载自blog.csdn.net/qq_40028201/article/details/89673264