12.1.2 直接管理内存
C++ 定义了两个运算符来分配和释放动态内存。运算符 new 分配内存,delete 释放 new 分配的内存。
相对于智能指针,这两个运算符管理内存非常容易出错。使用智能指针的程序更容易编写和调试。
在学习拷贝控制之前,除非使用智能指针来管理内存,否则不要分配动态内存。
使用 new 动态分配和初始化对象
在自由空间分配的内存是无名的,因此 new 无法为其分配的对象命名,而是返回一个指向该对象的指针:
int *pi = new int; // pi 指向一个动态分配的、未初始化的无名对象
此 new 表达式在自由空间构造一个 int 型对象,并返回该对象的指针。
默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的,而类类型对象将使用默认构造函数进行初始化:
string *ps = new string; // 初始化为空 string
int *pi = new int; // pi 指向一个未初始化的 int
可以使用直接初始化的方式来初始化一个动态分配的对象。可以用传统的构造方式,在 C++ 11 标准下,也可以用列表初始化的方式。
同样地,可以对动态分配的对象进行值初始化。
在 C++ 11 标准下,如果我们提供了一个括号包围的初始化器,就可以用 auto 从此初始化器来推断我们想要分配的对象的类型。但由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有单一初始化器时才可以用 auto。
auto p1 = new auto(obj); // p 指向一个与 obj 类型相同的对象
// 该对象用 obj 初始化
auto p2 = new auto{a, b, c}; // 错误❌: 括号中只能有单个初始化器
动态分配的 const 对象
用 new 分配 const 对象是合法的:
const int *pci = new const int(1024);
const string *pcs = new const string; // 默认初始化一个 const 的空的 string
一个动态分配的 const 对象必须初始化。对于定义了默认构造函数的类类型,const 动态对象可以隐式初始化,而其它类型的对象必须显式初始化。由于分配的对象是 const 的,new 返回的指针是一个指向 const 的指针。
内存耗尽
一旦一个程序用光了所有可用的内存,new 表达式就会失败。默认情况下,如果 new 不能分配所要求的内存空间,就会抛出一个类行为 bad_alloc 的异常。我们可以改变使用 new 的方式来阻止它抛出异常:
int *pi = new int; // 如果分配失败, new 抛出 std::bad_alloc
int *p2 = new (nothrow) int; // 如果分配失败, new 返回一个空指针
释放动态内存
为了防止内存耗尽,在动态内存使用完毕后,必须将其归还给系统。通过 delete 表达式 来将动态内存归还给系统。delete 接受一个指针,指向我们要释放的对象。
指针值和 delete
通常情况下,编译器不能分辨一个指针指向的对象是静态的还是动态分配的。类似地,编译器不能分辨一个指针所指向的内存是否已经释放。
动态对象的生存期直到被释放时为止
由内置指针(而不是智能指针)管理的动态内存在被显式释放之前都将会一直存在。
delete 之后重置指针值 …
当 delete 一个指针后,指针值就无效了。虽然指针已经无效,但在很多机器上指针仍然仍然保存着动态内存的地址。delete 之后,指针变为“空悬指针”。和未初始化的指针类似,空悬指针同样是危险的。
安全的做法是在 delete 之后将 nullptr 赋予空悬指针,使它不再指向任何对象。
… 这只提供了有限的保护
动态内存的一个基本问题是可能有多个指针指向相同的内存。在 delete 之后重置指针的方法只对这个指针有效,而其它任何仍指向(已经被释放)的内存的指针是没有作用的。