动态对象的生存期由程序来控制,当动态对象不再使用时,程序必须显式地销毁它们。
动态内存与智能指针
C++11新标准库提供两种智能指针类型
管理动态对象。智能指针行为类似常规指针,重要区别是智能指针负责自动释放所指向的对象。两种智能指针的区别在于管理底层底层的方式:shared_ptr
允许多个指针指向同一个对象;unique_ptr
则独占所指向的对象。标准库还定义了一个名为weak_ptr
的伴随类,它是一种弱引用,指向shared_ptr
所管理的对象。三种类型都定义在memory
头文件中。
shared_ptr类
智能指针也是模板类,必须在尖括号内提供指针指向的类型参数。
shared_ptr和unique_ptr都支持的操作
1 | shared_ptr<T> 空智能指针,可以指向类型为T的对象 |
shared_ptr独有的操作
1 | make_shared<T>(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象 |
make_shared函数
最安全的分配和使用动态内存的方法是调用一个名为make_sharedde的标准库函数。在分配对象的同时就将shared_ptr与之绑定,从而避免无意中将同一块内存绑定到多个独立创建的shared_ptr上。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
1 | //p6指向一个动态分配的空vector<string> |
shared_ptr的拷贝和赋值
每个shared_ptr都有一个引用计数值,表示有多少个shared_ptr指向它的对象。
当我们拷贝一个shared_ptr,例如用一个shared_ptr初始化另一个shared_ptr,作为参数传递给函数以及作为函数的返回值时,它所关联的计数器会递增。
当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,例如局部的shared_ptr离开作用域时,计数器会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象,并释放它占用的内存。
当动态对象不再被使用时,shared_ptr类会自动释放动态对象。
如果将shared_ptr保存在一个容器中,记得使用erase删除那些不再需要的元素,从而释放相关内存。
直接内存管理
对动态分配的对象进行初始化通常是个好主意。
默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。
使用new和delete管理动态内存存在三个常见问题:
1 忘记delete内存,导致内存泄露
2 使用已经释放的内存
3 同一块内存重复释放。当两个指针指向相同的动态分配对象时,可能通过一个指针delete了,如果我们随后又delete第二个指针,自由空间就可能被破坏
当我们delete一个指针后,指针指向的内存就无效了,指针变成了空悬指针(dangling pointer)
。虽然指针指向的内存无效,但指针仍有可能保存着(已经释放了的)动态内存的地址,访问空悬指针将操作未知的错误,并且难以调试。
shared_ptr和new结合使用
定义和改变shared_ptr的其他方法:
1 | shared_ptr<T> p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型 |
我们需要向不能使用智能指针的代码传递一个内置指针,我们可以使用get返回的指针传递。但需要确保代码不会delete指针的情况下,才可以使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。
1 | shared_ptr<int> p(new int(42)); //引用计数为1 |
智能指针和异常
智能指针使用的基本规范:
不使用相同的内置指针值初始化(或reset)多个智能指针
不delete get()返回的指针
不使用get()初始化或reset另外一个智能指针
如果你使用了get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针也无效了
如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
unique_ptr
某个时刻只能有一个unique_ptr指向一个给定对象,因此unique_ptr不支持普通的拷贝或赋值操作。
标准库禁用了unique_ptr的拷贝和赋值,将其拷贝构造函数和赋值函数声明为delete的。因此,当调用unique_ptr的拷贝构造函数时,提示如下错误的编译信息:
1 | error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’ |
当unique_ptr被销毁时,它所指向的对象也被销毁。
unique_ptr特有的操作:
1 | unique_ptr<T> u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针 |
调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通常用来初始化另外一个智能指针或给另外一个智能指针赋值。但是,如果我们不用另外一个智能指针来保存release返回的指针,我们程序就要负责释放资源。
1 | //将所有权从p1(指向string Stegosaurus)转移给p2 |
传递unique_ptr参数和返回unique_ptr
我们可以拷贝和赋值一个将要被销毁的unique_ptr。例如可以从函数返回一个unique_ptr或者返回一个局部对象的拷贝:
1 | unique_ptr<int> clone(int p) |
weak_ptr
weak_ptr表示一种“弱共享对象”的智能指针,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。即使有weak_ptr指向对象,对象也还是被释放。
weak_ptr操作:
1 | weak_ptr<T> w 空weak_ptr 可以指向类型为T的对象 |
动态数组
new动态分配数组得到的是一个数组元素类型的指针,因此不能对动态数组调用begin或end,也不能用范围for语句来处理动态数组中的元素。
在C++11新标准中,我们可以使用元素初始化器的花括号列表初始化动态分配的数组:
1 | int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
释放动态数组时,数组中的元素按逆序销毁。
如果我们在delete一个指向数组的指针时忽略了方括号,或者在delete一个指向单一对象的指针时使用了方括号,其行为是未定义的。
为了用unique_ptr管理动态数组,必须在对象类型后面跟一对方括号:
1 | //up指向一个包含10个未初始化int的数组 |
shared_ptr不支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。
1 | //为了使用shared_ptr,必须提供一个删除器 |
allocator类
allocator类提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。