第十二章、动态内存与智能指针

智能指针

内存分类

  • 静态内存
    • 用来保存局部static的对象。
  • 栈内存
    • 保存函数内部的非static对象。
  • 堆内存(自由空间)
    • 用来动态分配内存。 (new | delete)

智能指针

  • 内存泄漏: 使用new分配内存之后,没有delete释放内存。指向这片内存的指针被销毁后,这片内存无法再被操控。造成了内存浪费,即内存泄漏。
  • 头文件: memory

分类

  • shared_ptr
    • 允许多个指针指向同一个对象。
  • unique_ptr
    • 独占所指向的对象。
  • weak_ptr
    • 伴随类, 是一种弱引用,指向share_ptr所管理的对象。

引用计数的实现

  • 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录与多少对象与正在创建的对象共享类型。当我们创建一个对象时,只有一个对象共享状态,因此将引用计数初始化为1

  • 拷贝构造函数不分配新的计数器,而是拷贝构造函数给定的对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定的对象的状态又被一个新用户所共享

  • 析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为0,则析构函数释放状态。

  • 拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,意味着他的共享状态没有用户,拷贝赋值运算符就必须销毁对象。

  • 计数器保存在动态内存中

  • 
    #include <string>  
    
    
    class HasPtr {  
    public:  
      // constructor allocates a new string and a new counter, which it sets to 1  
      HasPtr(const std::string &s = new std::string()) :  
          ps(new std::string(s)), i(0), use(new std::size_t (1)) {}  
      // copy constructor copies all three data members and increments the counter  
      HasPtr(const HasPtr &p) :  
          ps(p.ps), i(p.i), use(p.use) { ++ *use; }  
      HasPtr& operator= (const HasPtr& rhs) {  
          // increment the use count of the right-hand operand  
          ++*rhs.use;    
          if(--*use == 0) {   // then decrement this object’s counter  
              delete ps;      // if no other users  
              delete use;     // free this object’s allocated members  
          }  
          ps = rhs.ps;        // copy data from rhs into this object  
          i = rhs.i;  
          use = rhs.use;  
          return *this;       // return this object  
      }  
      ~HasPtr() {  
          if(--*use == 0) {  // if the reference count goes to 0  
              delete ps;     // delete the string  
              delete use;    // and the counter  
          }  
      }  
    
    private:  
      std::string *ps;  
      int i;  
      // member to keep track of how many objects share *ps  
      size_t *use;  // --> reference counter  
    
    };  
    

new delete

  • 运算符new分配内存,返回一个指向内存的指针。、
  • 对于内置类型 ,进行值初始化 int *pi=new int (); 很有必要,可以得到一个良好定义的值。
  • new也可以用来分配const对象,必须初始化。
    • const int *pi= new const int(1024);
    • new返回的指针是指向const的指针(底层const)
  • 内存耗尽: (nothrow)
    • int *pi=new int ; //分配失败,抛出 std::bac_alloc 异常。
    • int *pi= new (nothrow) int ; //就算分配失败,也不会抛出异常。
  • 使用delete释放动态内存。
    • 销毁给定指针指向的对象; 释放对应的内存。
    • 释放一块非new分配的地址,或者相同地址释放多次都是未定义的行为。
    • const对象的值虽然不能修改,但他可以被delete释放
  • 【空悬指针dangling pointer】
    • delete一个指针后,指针仍然保存着被释放的那片内存的地址。

shared_ptr

  • 自动销毁所管理的对象,并且释放相关联的内存。
  • 构造函数是explicit的, 拒绝隐式转换。不能使用拷贝复制……
    • 如果返回值是shared_ptr, 必须把一个临时的share_ptr类型对象绑定到要返回的指针上。
  • 智能指针一般用来指向动态内存。 如果要指向其他资源,必须定义自己的释放操作。 (其他资源使用delete不一定释放得了。)
  • 不要混合使用普通指针和智能指针(只允许share_ptr之间传值,避免指向同一块内存的两个智能指针相互独立统计引用次数),也不要用get初始化另一个智能指针或为智能指针赋值。
    • 如果将一个智能指针和一个内置指针都指向了同一块内存区域,则内存管理的责任就应该由智能指针负责,不应该再使用内置指针(那片内存可能已经被智能指针释放了,内置指针变成了空悬指针)。
  • reset成员可以令一个share_ptr指向别的动态地址。
    • p.reset();
    • p.reset(q);
    • p.reset(q,d);
    • 若p是唯一指向其对象的shared_ptr ,reset会释放此对象。
    • 若传递了q, 则令p指向q ,上述情况依然执行。
    • 若还传递了d,则调用d而不是delete来”释放内存”。
  • 发生异常时
    • 对于普通指针管理的动态内存,如果没有执行delete ,就算程序退出(1.程序执行完毕; 2.发生异常)了,也会发生内存泄漏。
    • 对于动态指针管理的内存,如果程序退出(主要指发生异常),仍旧会递减引用次数以及释放动态内存。
  • 在一些其他资源(非动态内存)上,也可以使用智能指针,但要声明自己定义的删除器。
    • 用于没有析构函数来清理对象使用的资源的类。
    • shared_ptr<connection> p(&c, end_connection);
    • c是智能指针p所指向的地址。 end_connection是一个删除器(是一个函数指针),相当于delete。
    • 在地址c的引用计数归0后,调用end_connection。

unique_ptr

  • 只能有一个unique_ptr指向一个内存区域。

  • 初始化unique_ptr必须采用直接初始化形式,不支持普通的拷贝或赋值操作。但可以拷贝或赋值一个即将销毁的unique_ptr

  • unique_ptr:不支持普通的拷贝或赋值,但可拷贝或赋值一个即将销毁的unique_ptr,如函数返回值;调用release或reset将指针的所有权从一个非const的unique_ptr转移给另一个unique_ptr;调用reset()或用nullptr赋值可以释放对象,release只会切断管理关系

  • unique_ptr的删除器:管理方式与share_ptr不同,删除器会影响到类型及构造过程;重载删除器必须在尖括号里指明删除器类型,并在参数列表里指定一个可调用对象(删除器)

weak_ptr

  • weak_ptr:是一种弱引用,指向shared_ptr的对象,但不改变引用计数;调用lock返回指向的shared_ptr对象,当对象计数器为0时返回空shared_ptr

动态数组

  • 分配动态数组
    • 使用new(int [size_t]);
    • 返回一个类型指针,指向第一个元素
    • 初始化时,可以提供一个元素初始化器的花括号列表。但是因为不能在括号中给出初始化器,所以不能用auto分配数组。
    • 可以分配一个空数组,返回类似尾后指针的指针。
  • 释放动态数组
    • 要使用 delete[] , 最后不能没有方括号,否则会在没有警告的情况下行为异常。
  • 动态数组的管理
    • 可以使用unique_ptr管理动态数组(unique_ptr

allocator类

  • 可以将内存分配和对象构造分离。分配的内存是原始的、未构造的。
发布了29 篇原创文章 · 获赞 3 · 访问量 7138

猜你喜欢

转载自blog.csdn.net/liu432linux/article/details/79241341