【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);
 
posted @ 2021-05-29 14:28  萌新的学习之路  阅读(62)  评论(0编辑  收藏  举报