【C++ Primer Chapter 12 总结】动态内存
1.为什么使用动态内存:
-
不知道需要多少对象
-
不知道需要的对象的确切类型
-
希望在多个对象之间共享数据
2.动态分配的对象的生命周期与创建它们的位置无关。 它们一直存在直到被明确释放。
3. 标准库定义了两种智能指针类型,用于管理动态分配的对象。智能指针确保在合适的时候自动释放它们所指向的对象。
4.程序有3个变量存储区域:1)静态存储:局部静态变量和类静态数据成员以及函数外定义的变量。2)栈存储:函数内定义的非静态变量。
5.在静态内存或堆栈内存中分配的对象由编译器自动创建和销毁。3)自由存储/堆内存池:在运行时由程序动态分配的对象。
6.C++中使用new和delete两个操作管理动态内存。
7.为了使使用动态内存更容易(更安全),新库提供了两种用于管理动态对象的智能指针类型。
-
shared_ptr
-
unique_ptr
-
weak_ptr
8.智能指针也是模板类型。使用make_shared<Type>()创建智能指针。
shared_ptr<Type> sp;
make_shared<Type>(args); // make_shared使用参数args构造给定类型Type的对象。
shared_ptr<Type> p(q);
p = q;
p.unique(); // 如果use_count()是1,true
p.use_count(); // 多少指针共享p指向的对象
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10, '9'); // p4指向string("99999999")
9. 每个shared_ptr有关联计数器,通常称为引用计数(reference count)。
-
每当复制shared_ptr对象时,该计数将递增。e.g.: 1)初始化另一个shared_ptr时;2)作为赋值的右操作数时;3)作为实参传递给函数;4)从函数中按值返回时。
-
当给shared_ptr赋新值以及shared_ptr本身被销毁时(例如本地shared_ptr超出作用域时),计数器将递减。
一旦shared_ptr的计数器变为零( 最后一个指向对象的shared_ptr被撤销时),shared_ptr将通过析构函数自动释放它所管理的对象。
shared_ptr的析构函数递减shared_ptr指向的对象的引用计数。如果计数变为零,shared_ptr析构函数将撤销shared_ptr所指向的对象,并释放该对象使用的内存。
auto p = make_shared<int>(42); auto q(p); // p和q指向同一个对象,即:p和q指向的对象有两个user auto r = make_shared<int>(42); // r指向的对象有一个user r = q; // 赋值使r指向新的地址 // q指向对象的use count + 1 // r指向对象的use count - 1 // r指向对象没有user了,对象被自动释放
10.如果两个对象共享数据(指向相同的容器数据),则不能轻易销毁底下的对象数据。
Blob<string> b1; // 假设Blob类型复制的时候共享数据 { // new scope Blob<string> b2 = {"a", "an", "the"}; b1 = b2; } // b2是局部变量被销毁,但是b2中的数据不能被销毁,b1和b2指向相同的数据
11.使用new和shared_ptr。
只能使用直接初始化智能指针。内置指针类型无法隐式转换为智能指针类型。
shared_ptr<int> p2(new int(1024)); shared_ptr<int> p1 = new int(1024); // error:使用‘=’是复制初始化,使用‘()’是直接初始化。 shared_ptr<int> clone(int p) { return new int(p); // error: 隐式转换到 shared_ptr<int> } shared_ptr<int> clone(int p) { return shared_ptr<int>(new int(p)); // // ok: 显示创造了 shared_ptr<int> from int* }
12.shared_ptr只能与作为其自身副本的其他shared_ptr协调销毁(使用复制操作)。
使用make_shared可以在分配对象的同时将shared_ptr绑定到该对象。 这样就不会无意中将同一内存绑定到多个独立创建的shared_ptr。
void process(shared_ptr<int> ptr) { // use ptr } shared_ptr<int> p(new int(42)); // 引用计数是 1 process(p); // 复制p增加p的count; 在process函数里引用计数为2 int i = *p; // process函数结束后,p绑定的对象的user为1 int *x(new int(1024)); // x是个普通指针,指向int对象 process(x); // error: 无法将 int* 转换到 shared_ptr<int> process(shared_ptr<int>(x)); // 调用process函数,智能指针临时对象tmp和ptrp指向x指向的对象,在process函数结束后,智能指针指向的对象的user变为0,,该对象将被释放,但是x还是指向该对象!这句之后x将未定义 int j = *x; // error:x指向undefined
使用内置指针访问智能指针所拥有的对象是危险的,因为我们可能不知道该对象何时被撤销。
13. 智能指针类型定义了一个名为get的函数,可以返回一个内置指针指向智能指针所管理的对象。
shared_ptr<int> p(new int(42)); int *q = p.get(); { // 未命名的智能指针,两个独立的智能指针指向相同的内存,因为p和q是彼此独立创建的,所以p和q的引用计数都为1 shared_ptr<int>(q); } // {}结束,q被销毁,q的引用计数都减少为0,q指向的对象被释放 int foo = *p; // p未定义,p指向的内存被释放了 14.智能指针指向新的对象。 if (!p.unique()) { p.reset(new string(*p)); }
15.使用智能指针:
-
不要使用相同的内置指针值来初始化(或重置)多个智能指针。这些智能指针会指向同一个对象但是有不同的reference count。
-
不要删除get()返回的指针。get()返回的指针指向和智能指针一样的对象。
-
不要使用get()来初始化或重置另一个智能指针。
-
如果使用get()返回的指针,请记住,当最后一个相应的智能指针消失时,该指针将失效。
16. 直接管理内存。
-
new操作符分配内存。
-
delete释放new分配的内存。
分配在自由存储上的对象是未命名的,所以new无法命名它分配的对象。相反,new返回一个指向它分配的对象的指针。
默认情况下,动态分配的对象是默认初始化的,这意味着内置或复合类型的对象具有未定义的值,类类型的对象由其默认构造函数初始化。
销毁指针指向的对象,并释放对应的内存(指针还是可以继续使用,指向其他对象)。
int *pi = new int; // pi指向动态分配的,未命名的,未初始化的int int *pi = new int(); // 值初始化 *pi = 0 const int *pci = new const int(1024); // pci指向const int delete p; // p必须指向动态分配的对象或者nullptr
17. unique_ptr指针。
每次只有一个unique_ptr可以指向给定的对象。
当unique_ptr对象被撤销时,unique_ptr对象所指向的对象也被撤销。
unique_ptr<Type> up; unique_ptr<Type, Delete> up(d); up.release(); up.reset(q); unique_ptr<double> p1; unique_ptr<int> p2(new int(42));
18.因为unique_ptr拥有它所指向的对象,所以unique_ptr不支持普通的复制或赋值。
我们可以通过调用release或reset将unique_ptr(非const)指向的对象的拥有权转换为另一个unique_ptr。
调用release会中断unique_ptr与它所管理的对象之间的连接。 release返回的指针通常用于初始化或赋值另一个智能指针。
如果我们不使用另一个智能指针来保存释放返回的指针,程序将负责释放该资源。
unique_ptr<string> p2(p1.release()); // release使p1为nullptr,p2可以绑定到p1原指向的对象了 unique_ptr<string> p3(new string("Trex")); p2.reset(p3.release()); // p2指向p3原指向的对象 auto p = p2.release(); delete p;
19. 特殊情况:可以复制或赋值一个即将销毁的unique_ptr对象。最常见的例子是从函数返回unique_ptr对象。
unique_ptr<int> clone(int p) { // 按值返回 unique_ptr<int> ret(new int (p)); // . . . return ret; }
20.weak_ptr。
unique_ptr<int> clone weak_ptr<Type> wp; weak_ptr<Type> wp(sp); wp = sp; wp.reset(); wp.use_count(); wp.expired(); // true, 如果use_count()为0 wp.lock(); // 检查weak_ptr所指向的对象是否仍然存在,返回一个指向共享对象的shared_ptr。
21.weak_ptr指向由shared_ptr管理的对象。
将weak_ptr绑定到shared_ptr不会改变该shared_ptr的引用计数。
一旦指向该对象的最后一个shared_ptr消失,该对象本身将被删除。即使有weak_ptrs指向该对象,该对象也将被删除。
因此不能使用weak_ptr对象直接访问其对象。
auto p = make_shared<int>(42); weak_ptr<int> wp(p); if (shared_ptr<int> np = wp.lock()) { // true if np is not null // np与p共享对象 }
22.动态数组。动态数组没有数组类型,只能通过指向数组的指针访问数组。
int *pia = new int[arr_size]; typedef int arrT[42]; int *p = new arrT; int *pia2 = new int[10](); // 值初始化 int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9}; delete [] pa; // 释放数组内存
23.使用智能指针管理动态数组。
当unqiue_ptr对象指向数组时,可以使用下标操作符来访问数组中的元素。
Shared_ptrs不提供对管理动态数组的直接支持。如果希望使用shared_ptr来管理动态数组,则必须提供自己的删除器。
unique_ptr<int[]> up(new int[10]); up.release(); for (size_t i = 0; i != 10; ++i) up[i] = i; shared_ptr<int[]> sp(new int[10], [](int *p) { delete[] p; }); sp.reset(); for (size_t i = 0; i != 10; ++i) *(sp.get() + i) = i;
24. new操作将分配内存和在内存中构造对象结合起来。 delete结合了销毁指针和释放对象内存。
当我们分配内存块时,我们想要根据需求在该内存中构造对象。
从分配中耦合构造意味着我们可以在大块中分配内存,并且只在实际需要创建对象时才支付构造对象的开销。
allocator<Type> a; a.allocate(n); a.deallocate(p, n); a.construct(p, args); a.destory(p);
25.allocator类可以将分配与构造对象分开。
allocator<string> alloc; // object that can allocate strings auto const p = alloc.allocate(n); // allocate n unconstructed strings auto q = p; // q will point to one past the last constructed element alloc.construct(q++); // *q is the empty string alloc.construct(q++, 10, 'c'); // *q is cccccccccc alloc.construct(q++, "hi"); // *q is hi! while (q != p) alloc.destroy(--q);