程序用堆来存储动态分配的对象(即那些在程序运行时分配的对象),动态对象的生存期由程序来控制,当其不再使用时,我们的代码必须显式地销毁他们;
12.1 动态内存与智能指针
- C++中动态内存的管理是通过一对运算符new和delete来完成的,new在动态内存中为对象分配空间并返回一个指向该对象的指针,delete接受一个动态对象的指针并销毁该对象释放与之关联的内存;
- 新标准库提供两种智能指针管理动态对象,与常规指针的区别在于它负责自动释放所指向的对象;shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象,weak_ptr是一种弱引用,指向shared_ptr所管理的对象,这三种类型都定义在<memory>头文件中;
- shared_ptr类的操作详见401页,最安全的分配和使用动态内存你的方法是调用一个名为make_shared的标准库函数,类似顺序容器的emplace成员函数,make_shared用其参数来构造给定类型的对象,如果不传递任何参数,对象就会进行值初始化;
auto p6 = make_shared<vector<string>>(); p6指向一个动态分配的空vector<string>
我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,当拷贝一个shared_ptr(拷贝构造的三种情况)时计数器递增,当给shared_ptr赋予一个新值或者shared_ptr被销毁(例如一个局部的shared_ptr离开作用域)时,计数器递减;
// factory函数返回一个shared_ptr指针,指向<Foo>类型的对象 shared_ptr<Foo> factory(T arg) { return make_shared<Foo>(arg); } shared_ptr<Foo> use_factory(T arg) { shared_ptr<Foo> p = factory(arg); //p计数为1 return p; //return p使得p引用计数递增了一次,及时P离开作用域递减一次p的计数仍不为0,因此不会释放其内存 }
-
由于在最后一个shared_ptr销毁前内存都不会释放,因此需要保证shared_ptr在无用之后就不再保留,否则会浪费内存,有一种情况是shared_ptr保存在一个容器中,容器重排后不再需要全部元素,要用erase删除那些不在需要的shared_ptr元素;
-
程序使用动态内存主要有三个原因:程序不知道自己有多少个对象,程序不知道所需对象的准确类型,程序需要多个对象间共享数据;
-
将一个shared_ptr赋予另一个shared_ptr会递增赋值号右侧shared_ptr的引用计数,而递减左侧的引用计数。如果shared_ptr的引用计数变为0,它所指向的对象会被自动销毁。
-
如果提供了一个括号包围的初始化器,可以用auto从此初始化器来腿短我们想要分配的对象的类型,但是只能有单一初始化器时才可以使用auto;
auto p1 = new auto(obj) ; //可以,p指向与obj同类型的对象 auto p2 = new auto{a,b,c} ; //错误,括号中只能有单个初始化器
-
new后面可以传递额外的参数,这种形式的new为定位new,传递(nothrow)表示告诉它不能抛出异常,如果这种形式的new不能分配所需内存,它会返回一个空指针;
-
当delete一个指针后,指针值就变为无效,但很多机器上指针仍然保存着(已经释放了的)动态内存的地址,这时候这种指针就是所谓的空悬指针(dangling pointer);有一种方法可以避免空悬指针的问题,在指针即将要离开其作用域之前释放掉它所关联的内存;如果需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象;
-
如果不初始化一个只能指针,它就会被初始化为一个空指针,也可以用new返回的指针初始化智能指针,智能指针的构造函数是explicit的(说明只能直接初始化不能使用=拷贝初始化),同时一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针;
-
智能指针定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象,该指针虽然是一个内置指针但是不能用delete删除;永远不要用get初始化另一个智能指针或者为另一个智能指针赋值;
-
自己定义释放操作以及智能指针5个陷阱,详见417页;
-
unique_ptr “拥有”它指向的对象,只能有一个unique_ptr指向一个给定的对象,unique_ptr不支持普通的拷贝与赋值操作
unique_ptr<string>p1(new string("stegosaurus")); unique_ptr p2(p1); //错误,不支持拷贝 unique_ptr p3 = p2; //错误,不支持赋值
12.2 动态数组
-
使用new分配对象数组要在类型名后使用方括号如下所示:
int *pia = new int[get_size()]; //get_size()确定对象数目,必须是整型但不必是常量 typedef int arrT [42] ; //arrT表示 int[42]类型,即一个有42个int的数组 int *p = new arrT;
对于这里new int[]的理解,不能认为是分配了一个数组对象,而是得到一个数组元素类型的指针,指向第一个int元素
-
动态数组不是数组类型,不能对动态数组调用begin或end,这些函数使用数组维度来返回指向首元素和尾后元素的指针。出于相同的原因,也不能用范围for语句来处理动态数组中的元素;
-
动态分配一个空数组是合法的, char *cp = new char[0]; 正确但是cp不能解引用;此时cp保证与new返回的其他任何指针都不相同,就像尾后指针一样;
-
为了释放动态数组,需要在数组名前加上一个空方括号对; delete[] pa; pa必须指向一个动态分配的数组或为空;数组中的元素按逆序销毁,即最后一个元素首先被销毁,然后倒数第二个,以此类推;
12.3 文本查询程序
- 重点弄清楚shared_ptr在这个程序里的用途