《C++ Primer》读书笔记第十二章-1-动态内存与智能指针

来源:互联网 发布:足球滚球软件 编辑:程序博客网 时间:2024/05/21 19:36

笔记会持续更新,有错误的地方欢迎指正,谢谢!

这一章–>内存

  1. 全局对象在程序启动时分配内存,在程序结束时销毁;
  2. 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁;
  3. 局部static对象在第一次使用前分配,在程序结束时才销毁。

这些都是C++ 编译器帮我们管理的,对于这么爱赋予程序员自由的语言,C++也允许我们自己来决定,就是动态分配对象:动态分配的对象只有显式地被释放时,这些对象才会销毁。

另外,标准库定义了两个智能指针类型来帮助我们管理动态分配的对象,怎么帮助呢?当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。

通过不同方式定义的变量类型在内存中的存储位置也不同=>内存中,全局变量和静态变量存在于静态区(静态内存);局部非static变量存在于栈区(栈内存);new的对象和malloc的对象都存在于堆区(自由空间),自由空间中分配的对象直到显式释放或程序结束才被销毁;常量存在于常量区。

动态内存与智能指针

一、在C++中,动态内存的管理是通过一对运算符来完成:

  1. new,在动态内存中为对象分配空间,并且返回一个指向该对象的指针,可以选择对对象进行初始化;
  2. delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

二、这样我们在使用的时候很容易出问题:

  1. 忘记释放内存,会产生内存泄漏;
  2. 在尚有指针引用内存的情况下,释放了它,会产生引用非法内存的指针(也叫非法指针)。

说了这么多,终于引出了智能指针: 智能指针的行为类似常规指针,重要的区别是智能指针负责自动释放所指向的对象,它不支持指针算术运算,它管理底层指针的方式不同:(以下第一个和第二个都是智能指针,只是不同的智能指针罢了;第三个是弱引用而已~)

  1. shared_ptr允许多个指针指向同一个对象;
  2. unique_ptr独占所指向的对象;
  3. weak_ptr,弱引用,指向shared_ptr所管理的对象。

以上三种类型都定义在memory头文件中。

shared_ptr类

这里写图片描述

递增计数器:
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:

auto p = make_shared<int>(42);//p指向的int对象只有p一个引用者。auto q(p);//pq指向相同对象,此对象有两个引用者。

auto为shared_ptr 类型(即智能指针),类似指针的类型。

每个shared_ptr都有一个关联的计数器(引用计数): 无论何时我们拷贝一个shared_ptr,都会递增计数器,举例:

  1. 用一个shared_ptr初始化另一个shared_ptr;(如上第一、二行代码)
  2. 将它作为参数传递给一个函数;
  3. 作为函数的返回值。

递减计数器:

举例:
1. 给shared_ptr赋予一个新值;
2. shared_ptr被销毁。(例如:离开一个局部的shared_ptr的作用域)

一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象:

auto r = make_shared<int>(42); //r指向的int对象只有一个引用者。r = q; //1:给r赋新值,让它指向新地址;2:递增q指向对象的引用计数;//3:递减r原来指向的对象的引用计数;4:这个r原来指向的对象已没引用者,便会自动释放。

shared_ptr自动销毁所管理的对象:
类似构造函数,每个类都有一个析构函数用于对象的销毁。
shared_ptr的析构函数会递减它所指的对象的引用计数,如果计数为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

使用动态内存的场景:

  1. 程序不知道自己要使用多少对象(比如容器类。shared_ptr类 相似于vector,可灵活增减。);
  2. 程序不知道所需对象的准确类型(智能指针(包括shared_ptr和unique_ptr)也是模板。e.g. auto <T> name;);
  3. 程序需要在多个对象间共享数据(有兴趣的可以自己看看书上的例子)。

直接内存管理

前面已经学习了如何用智能指针来管理动态内存,比较简单(因为自己可以忽略很多细节)。

现在我们只借助C++提供的两个运算符来直接管理内存:

  1. new 分配内存;
  2. delete 释放new分配的内存 。

这一章越到后面,你越会觉得自己管理真是又烦又容易出错。

内置类型默认初始化为未定义,建议用圆括号直接初始化;只有当括号中仅有单一初始化器(初始化值)时才可以使用auto。

new的使用,动态分配内存:

int *pi = new int(4);string *ps = new string(10, '9');vector *pv = new vector{0, 1, 2, 3};

delete接受一个指针,指向我们要释放的对象:

int *p1 = new int(24); delete p1; //销毁p1指向的对象,释放对应内存

我们传递给delete的指针必须指向动态分配的内存(或是空指针)。释放一块不是new分配的内存,或者把相同的指针释放多次,其行为都是未定义的:

int i, *pi1 = &i, *pi2 = nullptr;double *pd1 = new double(33), *pd2 = pd1;delete i; //错误。i不是指针。delete pi1; //未定义的行为。因为尝试释放一块不是new分配的内存。delete pd1; //正确。delete pd2; //未定义。因为把相同的指针释放多次。delete pi2; //正确。空指针。

空悬指针:指向一块曾经保存数据对象但现在无效的内存的指针;delete会让该指针变成空悬指针,此时建议用nullptr给它。
例子:

double *dp= new double(33);delete dp;    //dp变成一个空悬指针。dp = NULL;    //dp不再是空悬指针。

shared_ptr和new结合使用

初始化智能指针:

  1. 不能进行内置指针到智能指针的隐式转换,必须使用直接初始化形式;
  2. 智能指针默认使用delete释放,所以用于初始化智能指针的普通指针必须指向动态内存(new出来的)。
  3. 管理的资源不是new分配的内存,则必须传入一个删除器替代delete。

例子:

shared_ptr p1(new int(24)); //正确。直接初始化。shared_ptr p2 = new int(24); //错误。//等号右边,用new构造对象时返回的是一个普通指针int*,它试图隐式转换为智能指针。//但是我们的智能指针的构造函数很无情,不允许隐式转换,所以报错了。

智能指针和异常

一句话总结:使用智能指针的好处:即便代码出现没有catch到的异常,还是能保证内存的释放;最好不要使用普通指针!

unique_ptr

前面只是讲了两个智能指针中的一个shared_ptr,还有一个呢:unique_ptr。

unique_ptr独有它所指向的对象。不支持普通的拷贝或赋值,但可拷贝或赋值一个即将销毁的unique_ptr,如作为函数返回值:

//返回一个局部对象的拷贝unique_ptr Clone(int p){    unique_ptr ret(new int(p));    return ret; }

另外,可调用release或reset将指针的所有权从一个非const的unique_ptr转移给另一个unique_ptr。

智能指针补充:

新的标准库定义了智能指针类型——shared_ptrunique_ptrweak_ptr,可令动态内存管理更为安全。对于一块没任何用户使用它的内存,智能指针会自动释放它。所以,现代C++程序应该尽量使用智能指针。