第12章 动态内存
动态内存的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。
为了更安全地使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象。
当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数体之外的变量。
栈内存用来保存定义在函数体内的非static对象。
分配在静态或栈内存中的对象由编译器自动创建和销毁。
static对象在使用之前分配,在程序结束时销毁。
12.1 动态内存与智能指针
new在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;
delete接受一个动态对象的指针,销毁该对象,并释放与之关联的内存
为了更容易地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。
shared_ptr允许多个指针指向同一个对象
unique_ptr则“独占”所指向的对象
标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
三种类型都定义在memory头文件中
12.1.1 shared_ptr类
make_shared函数
shared_ptr<int> p3 = make_shared<int>(42); //指向一个值为42的int的shared_ptr shared_ptr<string> p4 = make_shared<string>(10, '9'); shared_ptr<int> p5 = make_shared<int>();
shared_ptr的拷贝和赋值
auto p = make_shared<int>(42); auto p(q); //p和q指向相同对象,此对象有两个引用者
auto r = make_shared<int>(42); r = q; //给r赋值,令它指向另一个地址 //递增q指向的对象的引用计数 //递减r原来指向的对象的引用计数 //r原来指向的对象已没有引用者,会自动释放
12.1.2 直接管理内存
string *ps = new string; //初始化为空string int *pi = new int; //pi指向一个未初始化的int
int *pi = new(1024); int *ps = new string(10, '9'); vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
string *ps1 = new string; string *ps2 = new string(); int *pi1 = new int; //默认初始化 *pi1的值未定义 int *pi2 = new int(); //值初始化为0 *pi2为0
auto p1 = new auto(obj); auto p2 = new auto{a,b,c}; //错误
const int *pci = new const int(1024); const string *pcs = new const string;
传递给delete的指针必须指向动态分配的内存,或者是一个空指针
const对象的值不能被改变,但它本身是可以被销毁的
const int *pci = new const int(1024); delete pci;
int *p(new int(42)); auto q = p; //pq指向相同的内存 delete p; //p和q均变为无效 p = nullptr;
12.1.3 shared_ptr和new结合使用
shared_ptr<double> p1; shared_ptr<int> p2(new int(42));
我们不能将一个内置指针隐式转换为一个智能指针
shared_ptr<int> p1 = new int(1024); //错误:必须使用直接初始化形式 shared_ptr<int> p2(new int(1024)); //正确:使用了直接初始化
shared_ptr<int> clone(int p){ return new int(p); //错误:隐式转换 } shared_ptr<int> clone(int p){ return shared_ptr<int>(new int(p)); //正确:显式 }
12.2 动态数组
int *p = new int[42];
初始化动态分配对象的数组
int *pia = new int[10]; //10个未初始化的int int *pia2 = new int[10](); //10个值初始化为0的int string *psa = new string[10]; //10个空string string *psa2 = new string[10](); //10个空string
初始化器初始化:
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9}; string *psa3 = new string[10]{"a", "an", "the"}; //初始化前几个
释放动态数组
delete [] pa; //pa必须指向一个动态分配的数组或为空
typedef int arrT[42]; //arrT是42个int的数组的类型别名 int *p = new arrT; //分配一个42个int的数组,p指向第一个元素 delete [] p; //方括号是必须的,
空悬指针
在delete之后,指针就变成了空悬指针。即指向一块曾经保存数据对象但现在已经无效的内存的指针。
可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。
12.2.2 allocator类
allocator分配未构造的内存
当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。
//分配比vi中元素所占用空间大一倍的动态内存 auto p = alloc.allocate(vi.size() * 2); //通过拷贝vi中的元素来构造从p开始的元素 auto q = uninitialized_copy(vi.begin(), vi.end(), p); //将剩余元素初始化为42 uninitialized_fill_n(q. vi.size(), 42);