[读书笔记] C++Primer (第5版) 第12章 动态内存

静态内存:用来保存局部static对象(类static数据成员以及定义在任何函数之外的变量)。

栈内存:保存定义在函数内的非static对象。

分配在静态内存和栈内存中的对象由编译器自动创建和销毁。

栈对象仅在其定义的程序块运行时才存在;static对象在使用前分配,程序结束时销毁。

1.动态内存:

   除了这两个,每个程序还拥有一个内存池。这部分内存被称为自由空间或堆,用来存储动态分配的对象。

   动态内存的管理是通过一对运算符来完成的:

  • new   在动态内存中为对象分配空间并返回一个指向该对象的指针
  • delete  接受一个动态对象的指针,销毁该对象,并释放与之关联的内存 

   尚有指针引用内存,就释放该指针了,会产生引起非法内存的指针。

   智能指针来管理动态对象,负责自动释放所指向的对象。

   标准库有shared_ptr,unique_ptr和weak_ptr三种,都定义在memory头文件中。

2.shared_ptr类:

   智能指针也是模板,创建时提供可以指向的类型。  shared_ptr sp;    // 指向string类型的智能指针

   默认初始化的智能指针中保存着一个空指针。

   解引用(*)一个智能指针返回它所指向的对象。

  • make_shared(args)  最安全的分配和使用方法。

   每个shared_ptr都有一个关联的计数器,通常称为 引用计数。

   拷贝一个shared_ptr计数器会递增。

   赋值:auto r = make_ptr(42);       // r指向的int只有一个引用者

              r = q;           // 递减r原来指向对象的引用计数,递增q原来指向的引用计数

   当shared_ptr被赋新值,被销毁或离开其作用域,计数器会递减。

   一个shared_ptr的计数器为0时,会自动释放自己所管理的对象(使用析构函数)。

   当shared_ptr存在容器中时,不需要时要用erase删除那些不用的元素。

   使用动态内存出于以下三种原因:

  •  程序不知道自己需要多少对象(容器类就是这个原因)
  • 程序不知道所需对象的类型
  • 程序需要在多个对象间共享数据(如果两个对象共享底层元素,某个对象被销毁时,我们不能单方面销毁底层数据。因为可能另一个对象还在使用底层元素。)

3.new表达式:

   在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。

   int* pN = new int;           // pN指向一个动态分配的,未初始化的无名对象

   new在自由空间构造一个int型对象,并返回指向该对象的指针。

   默认情况,动态分配的对象是默认初始化的。内置和组合类型的对象的值将是未定义的,类类型对象将用默认构造函数。

   也可以使用直接初始化的方式:int* pN = new int(42);          // nP指向的对象的值为42

   也可以使用值初始化(类型名之后跟一对空括号):     int* pN = new int();        // 值初始化为0 

   定义了自己初始化函数的类来说,值初始化是和不加()是一样的。对于内置对象,一个是未定义,一个是默认值。

   动态分配的const对象必须进行初始化(有默认构造函数的可以使用隐式初始化)。

4.内存耗尽:

   一个程序用光了它所有的可用内存,new表达式就会出错。

   默认情况,new不能分配所需的内存空间,会抛出bad_alloc的异常。

   int* p = new(nothorw) int;         // 分配失败返回一个空指针

   这种形式的new为定位new,允许向new传递额外的参数。

5.delete表达式:

   delete表达式执行两个动作:销毁指针指向的对象,释放对应的内存。

   传递给delete的指针必须指向动态分配的内存或空指针。

   释放一块非new分配的内存,或将相同的指针释放多次,行为是未定义的。

   空悬指针:delete后的指针。指向一块曾保存数据对象,但现在无效的内存的指针。(未初始化指针的所有缺点它都有)

   内置指针可以在delete之后置空,来检测是否delete过。

   多个指针指向相同的内存时,delete只对这个指针有效。例如:  q = p; delete p; p = nullptr;   // p被释放了,但是q也变成无效了

6.shared_ptr和new结合使用:

   shared_ptr p(new int(565));     // 智能指针接收指针参数的构造函数是explicit的,所以不能将内置指针隐式转换成智能指针

   shared_ptr p = new int(3344);    // 错误。  构造函数是explicit的例子

   默认情况,用来初始化智能指针的普通指针必须指向动态内存,因为智能指针使用delete表达式释放。

   也可将智能指针绑定   指向其他资源的指针,但是必须提供自己的操作代替delete。

[读书笔记] C++Primer (第5版) 第12章 动态内存_第1张图片

[读书笔记] C++Primer (第5版) 第12章 动态内存_第2张图片

   推荐使用make_shared而不是new的原因:在分配对象时就与shared_ptr绑定,避免同一块内存指向多个shared_ptr。

   当一个shared_ptr绑定到普通指针时,就将内存管理的责任交给了shared_ptr,就不应该再使用内置对象来访问了。

   使用内置对象访问是一个智能指针负责的对象很危险的,因为我们不知道对象何时被销毁。

   智能指针定义一个get函数,返回一个内置指针。(确定不会delete该内置指针,才能使用get函数)。

7.指针和异常:

   函数的退出有两种可能:正常处理结束和发生了异常。无论哪种局部对象都会被销毁,智能指针同样。

   对于直接管理内存,在new和delete之间发生异常(并且所在函数没有捕获异常),则该内存就不会被释放了。

8.unique_ptr:

   某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,所指向的对象也会被销毁。

   没有make_shared返回unique_ptr的操作,只能绑定new返回的指针。

   由于一个unique_ptr拥有它所指向的对象,所以不支持拷贝和赋值。(但是可以拷贝或赋值一个将要被销毁的unique_ptr,例如unique_ptr可以作为形参和返回值)

   [读书笔记] C++Primer (第5版) 第12章 动态内存_第3张图片

   unique_ptr p2(p1.release());       p1调用release返回指针,并置空p1;

   auto_ptr(标准库的较早版本)具有unique_ptr的部分特征。但是auto_ptr不能在容器中保存,不能作为返回值。

   可以向unique_ptr中传递删除器,类似关联容器的比较操作,在<>的第二个参数传入删除函数指针

9.weak_ptr:

   不控制所指向对象生存期的智能指针,指向一个shared_ptr管理的对象。

   [读书笔记] C++Primer (第5版) 第12章 动态内存_第4张图片

   将一个weak_ptr绑定在一个shared_ptr不会改变shared_ptr的引用计数。

   一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,该对象还是会被释放。

   这种“弱”共享的形式,weak_ptr在访问对象时,对象可能不存在,访问前要用lock()检查一下.

10.动态数组:

    有时需要一次为很对对象分配内存。(如vector、string都是在连续的内存保存元素的)

    两种一次分配一个对象数组的方法:

  • 另一种new表达式语法:可以分配并初始化一个对象数组
  • 名为allocator的类:允许将分配和初始化分离

    大多数应用应该使用标准库容器,而不是动态数组。使用容器更简单,内存管理不会出错并且性能更高。

    int* pia = new int [43];     // 分配一个43个int的数组类型,返回指向第一个int的指针

    由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin和end。

    new分配的对象,不管是单个分配还是数组分配,都使用默认构造初始化。

    也可以使用值初始化:int* pia = new int [43]();        // 43个值为0的int型数据

    新标准中可以使用初始化器(花括号列表进行初始化),初始化器数目小于元素数目,剩余元素进行值初始化,初始化器大于元素数目,会new失败,不会分配任何内存,返回bad_array_new_length的异常,类似bad_alloc。

    动态分配一个空数组是合法的。返回一个合法的非空指针(与其他任何指针都不同,类似尾后指针)。

    delete []p;            // 释放动态数组

    忽略方括号(或对指向单一对象的指针使用方括号),行为是未定义的,可能程序在没有任何警告下行为异常。

    标准库提供一个可以管理new分配的数组的unique_ptr版本。

    unique_ptr  up(new int[12]);             // up指向包含10个未初始化的int数组

    up.release();                                               // 自动用delete [] 销毁其指针

    不能使用点和箭头运算符了,可使用[]下标访问。

    shared_ptr不支持管理动态数组,如果想使用的话,必须提供删除器。

    shared_ptr sp(new int[54], [](int* p){delete []p;  });      // 使用lambda表达式传入删除器

    sp.reset();         // 使用提供的lambda释放数组。

    shared_ptr未定义下标运算符,使用get获得到内置对象(注意:get到的对象不能使用delete,防止delete多次造成错误)。

11.allocator类:

    标准库allocator类定义在memory头文件,它帮助我们将呢欧村分配和对象构造分离开来。

    allocator是一个模板,分配内存时需要根据给定对象,来确定适当的内存大小和对齐位置。

    allocator alloc;           // 可以分配string的allocator对象

    auto const p = alloc.allocate(n);      // 分配n个未初始化的string

    [读书笔记] C++Primer (第5版) 第12章 动态内存_第5张图片

    allocator分配的内存是未构造的,使用construct进行构造。

    还未构造对象的情况,就使用原始内存,行为是未定义,严重错误!

    allocator.construnt(p, "hello");   

    allocator.destroy(p);      // 释放我们真正构造的string,只能对真正构造了的元素使用destroy

    元素被销毁后,可以重新使用这块内存保存其他string,也可归还给系统。释放内存通过deallocate()。

    alloc.deallocate(p, n);    // p不能为空,指向由allocate分配的内存。n必须与调用allocate分配内存时的值一样。

    标准库还提供了拷贝和填充算法:

    [读书笔记] C++Primer (第5版) 第12章 动态内存_第6张图片

    auto p = alloc.allocate(vec.size() * 2);         // 分配vector大小2倍的动态内存

    // 拷贝vec中的元素,构造从p开始的元素,p必须是未构造的内存,返回指针指向最后一个构造元素之后的位置

    auto q = uninitialized_copy(vec.begin(), vec.end(), p); 

    uninitiallized_fill(q, vec.size(), 23);        // 将剩余元素初始化为23

 

你可能感兴趣的:(C++)