笔记会持续更新,有错误的地方欢迎指正,谢谢!
这一章–>内存
- 全局对象在程序启动时分配内存,在程序结束时销毁;
- 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁;
- 局部static对象在第一次使用前分配,在程序结束时才销毁。
这些都是C++ 编译器帮我们管理的,对于这么爱赋予程序员自由的语言,C++也允许我们自己来决定,就是动态分配对象:动态分配的对象只有显式地被释放时,这些对象才会销毁。
另外,标准库定义了两个智能指针类型来帮助我们管理动态分配的对象,怎么帮助呢?当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
通过不同方式定义的变量类型在内存中的存储位置也不同=>内存中,全局变量和静态变量存在于静态区(静态内存);局部非static变量存在于栈区(栈内存);new的对象和malloc的对象都存在于堆区(自由空间),自由空间中分配的对象直到显式释放或程序结束才被销毁;常量存在于常量区。
动态内存与智能指针
一、在C++中,动态内存的管理是通过一对运算符来完成:
- new,在动态内存中为对象分配空间,并且返回一个指向该对象的指针,可以选择对对象进行初始化;
- delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
二、这样我们在使用的时候很容易出问题:
- 忘记释放内存,会产生内存泄漏;
- 在尚有指针引用内存的情况下,释放了它,会产生引用非法内存的指针(也叫非法指针)。
说了这么多,终于引出了智能指针: 智能指针的行为类似常规指针,重要的区别是智能指针负责自动释放所指向的对象,它不支持指针算术运算,它管理底层指针的方式不同:(以下第一个和第二个都是智能指针,只是不同的智能指针罢了;第三个是弱引用而已~)
- shared_ptr允许多个指针指向同一个对象;
- unique_ptr独占所指向的对象;
- weak_ptr,弱引用,指向shared_ptr所管理的对象。
以上三种类型都定义在memory头文件中。
shared_ptr类
递增计数器:
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
auto p = make_shared<int>(42);//p指向的int对象只有p一个引用者。
auto q(p);//p和q指向相同对象,此对象有两个引用者。
auto为shared_ptr 类型(即智能指针),类似指针的类型。
每个shared_ptr都有一个关联的计数器(引用计数): 无论何时我们拷贝一个shared_ptr,都会递增计数器,举例:
- 用一个shared_ptr初始化另一个shared_ptr;(如上第一、二行代码)
- 将它作为参数传递给一个函数;
- 作为函数的返回值。
递减计数器:
举例:
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的析构函数就会销毁对象,并释放它占用的内存。
使用动态内存的场景:
- 程序不知道自己要使用多少对象(比如容器类。shared_ptr类 相似于vector,可灵活增减。);
- 程序不知道所需对象的准确类型(智能指针(包括shared_ptr和unique_ptr)也是模板。e.g.
auto <T> name;
); - 程序需要在多个对象间共享数据(有兴趣的可以自己看看书上的例子)。
直接内存管理
前面已经学习了如何用智能指针来管理动态内存,比较简单(因为自己可以忽略很多细节)。
现在我们只借助C++提供的两个运算符来直接管理内存:
- new 分配内存;
- 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结合使用
初始化智能指针:
- 不能进行内置指针到智能指针的隐式转换,必须使用直接初始化形式;
- 智能指针默认使用delete释放,所以用于初始化智能指针的普通指针必须指向动态内存(new出来的)。
- 管理的资源不是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_ptr
、unique_ptr
和weak_ptr
,可令动态内存管理更为安全。对于一块没任何用户使用它的内存,智能指针会自动释放它。所以,现代C++程序应该尽量使用智能指针。