【Effective C++ 条款13-17 笔记】【以对象管理资源】【在资源管理类中小心copying行为】【在资源管理类中提供对原始资源的访问】【成对使用mew和delete时要采取相同形式】..

条款13:以对象管理资源

动态分配对象时,对象存储在heap上,若不及时或者忘了delete对象指针,会造成内存泄露

即便最后没有忘记delete对象指针,在函数运行到delete语句之前,可能会遇到以下状况使得delete语句不被执行:

  • new和delete之间有一个过早的return
  • new和delete位于某个循环内,该循环由于某个continue、break或者goto过早退出
  • delete语句之前抛出异常,直接跳转到异常处理函数

为了防止资源泄露,主要有两点要注意:

  1. 获得资源后立即放进资源的管理对象内
  2. 资源的管理对象执行析构函数来释放资源

常用的资源管理对象有auto_ptr、shared_ptr

其中auto_ptr允许资源剥夺,即同一时刻只有一个auto_ptr指向资源,那么就不符合传统的copy理念

class A {
    
    
    ...
}

A* createA();	//工厂函数,返回一个指向动态创建class A对象的指针

void f() {
    
    
    auto_ptr<A> pA1(createA());	//pA1指向class A对象
    auto_ptr<A> pA2(pA1);	//pA2剥夺pA1占有的资源,pA1为null
}

因此,为了实现一般意义上的对象资源拷贝,可以使用shared_ptr来替换auto_ptr

shared_ptr使用引用计数的机制,计数器持续追踪有多少shared_ptr对象指向这份资源,当计数器=0的时候才会释放资源,每一次拷贝动作,就是计数器+1

class A {
    
    
    ...
}

A* createA();	//工厂函数,返回一个指向动态创建class A对象的指针

void f() {
    
    
    shared_ptr<A> pA1(createA());	//pA1指向class A对象
    shared_ptr<A> pA2(pA1);	//pA2与pA1指向同一个class A对象
}

注意,auto_ptr和shared_ptr在析构时都是delete而不是delete[],因此如下形式虽然能通过编译,但是与原本目的并不相同

auto_ptr<string> pStr(new string[10]);

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

有时候,一些资源并不是存储在heap上的,也就不能使用auto_ptr或者shared_ptr来管理资源了,这时候需要自定义一个资源管理class

自定义的资源管理class需要遵循:调用资源管理class的构造函数时,应该传入需要管理的资源(如指向资源的指针),在调用资源管理class的析构函数时,确保管理的资源被正确释放。这也就是**{资源在构造期间获得,在析构期间释放}——RAII**的解释

那么当一个资源管理class的对象被复制时,一般有如下几种策略:

  1. 禁止复制。通过条款06中的父类unCopyable来实现对copying函数(拷贝构造函数、拷贝赋值运算符)的禁用。
  2. 底层资源引用计数。也就是允许RAII对象指向的底层资源的浅copy操作。只需要内含一个shared_ptr成员变量即可,注意shared_ptr当引用计数=0的时候默认是释放资源,但是这里的“释放资源”操作是可以自定义的(比如替换为释放互斥锁)
  3. 复制底部资源。也就是进行深copy
  4. 转移底部资源所有权。也就是新的RAII对象可以剥夺旧的RAII对象占有的资源

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

资源管理class除了具备RAII的性质之外,最好对外提供能够访问原始资源的手段:提供一种方法能够将RAII class对象转换为其所含有的原始资源

这里的手段主要有(以shared_ptr为例):

  • 显式转换:shared_ptr提供一个成员函数get(),来执行显示转换

    shared_ptr<A> pA(createA());
    A* pAOri = pA.get();	//显示转换为原始资源的指针
    
  • 隐式转换:shared_ptr重载了operator->和operator*

    int a = pA->a;	//a是class A的成员变量
    

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

使用new会做两件事:

  1. 调用operator new函数分配内存
  2. 在分配出的内存上调用构造函数

同样,使用delete也会做两件事:

  1. 在已分配的内存上调用析构函数
  2. 调用operator delete函数释放内存

而使用delete的问题在于:在即将被释放的内存中,有多少个对象,这决定了调用多少个析构函数

当使用delete[]时,delete认为将要操作的指针指向一个数组,否则delete认为将要操作的指针指向单个对象

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

newed对象,看时态,就是在独立语句(该语句只完成这一功能)中使用智能指针指向new出来的对象

为什么要用独立语句呢,因为:

void func1(shared_ptr<A> pA, int b);
int func2();

//传入实参
func1(shared_ptr<A>(new A), func2());

这里其实有三件事,按照理想顺序应该是:

  1. 执行new A
  2. 调用shared_ptr构造函数
  3. 调用func2()

但是,C++编译器并不一定会按照这个顺序完成这三件事,实际的顺序很可能是:

  1. 执行new A
  2. 调用func2()
  3. 调用shared_ptr构造函数

如果在这个过程中,在第2步出现了异常,那么new出来的class A对象没有置入智能指针,并且没有delete,就会造成内存泄漏

猜你喜欢

转载自blog.csdn.net/weixin_44484715/article/details/121409611