YouTube视频链接
C++的智能指针
本文是ChernoP43视频的学习笔记。
智能指针是实现分配内存、释放内存这一过程自动化的一种方式。若使用智能指针,当我们调用new时不需要调用delete,甚至不需要调用new。智能指针本质上是一个原始指针的包装,当创建一个智能指针,它会调用new并为其分配内存,基于这个智能指针的内存会在某一时刻自动释放。
unique_ptr(优先)
unique_ptr是作用域指针,超出作用域时它会被销毁,然后调用delete。我们不能复制一个unique_ptr,因为如果复制一个unique_ptr会有两个指针,两个unique_ptr指向同一个内存块。如果其中一个死了,它会释放那段内存,而另一个unique_ptr指针就会指向被释放的内存。
要访问智能指针,首先要包括memory头文件。如果想要在特定的作用域下(两个大括号)创建一个unique_ptr来分配Entity,可以调用构造函数然后输入new Entity()。
std::unique_ptr<Entity> entity=new Entity();
但是会出现错误,因为unique_ptr的构造函数的是explicit的,需要显示调用构造函数,不能包含隐式转换。如果想要调用一个函数只需要通过剪头操作符来访问。
std::unique_ptr<Entity> entity(new Entity());
entity->Print();
一个更好的方法是把entity赋值给std::make_unique,主要是因为异常安全。如果构造函数抛出异常,使用make_unique(C++14)会保证最终得到的不是没有引用的悬空指针,从而造成内存泄漏。
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
entity->Print();
在第27行设置断点,F5运行发现Entity被创建出来,再按两次F10离开这个作用域,Entity就被销毁了。
查看unique_ptr的定义发现拷贝构造函数和拷贝构造操作符实际上被删除了。
shared_ptr
shared_ptr是共享指针,它实现的方式实际上取决于编译器和在编译器中使用的标准库。shared_ptr的工作方式是通过引用计数,引用计数基本上是一种方法,可以跟踪我们的指针有多少个引用,一旦引用计数达到0,它就会被删除。
在unique_ptr中不直接调用new的原因是因为异常安全,但是在shared_ptr中有所不同。shared_ptr需要分配另一块内存,叫做控制块,用来存储引用计数。如果创建一个new Entity然后将其传递给shared_ptr构造函数,它必须做两次内存分配:先做一次new Entity的分配,然后是shared_ptr的控制内存块的分配。若使用make_unique就可以把它们组合起来。
这里有两个作用域,在外面的作用域中有了e0,里面的作用域中有了sharedEntity,然后把e0赋值给sharedEntity。在27行设置断点按F5运行,再按F10发现Entity被创建了。当里面的作用域死亡时,sharedEntity就会死亡,但是e0还存活并且持有对该Entity的引用,所以这里没有调用析构函数。
若再按F10Entity就会被销毁。当所有的引用都消失,当所有的栈分配对象,追踪shared_ptr的从内存释放后,底层的Entity才会被删除(调用析构函数)。
weak_ptr
weak_ptr被称为弱指针,可以和shared_ptr一起使用。它只是像声明其他东西一样声明,可以给它赋值为sharedEntity。这里和之前复制sharedEntity所做的一样,但是这里不会增加引用计数。当我们将一个shared_ptr赋值给另外一个shared_ptr时它会增加引用计数。但是当把一个shared_ptr赋值给一个weak_ptr时不会增加引用计数。
如果不想要Entity的所有权,例如在排序一个Entity列表时不关心它们是否有效,只需要存储一个它们的引用就可以了。我们可能会问weak_ptr底层对象是否还存活,但它不会保持底层对象存活,因为它不会增加引用计数。
若把之前的shared_ptr换成一个weak_ptr。在27行设置断点按F5运行,再按F10发现Entity在这里被创建,然后它会被分配到sharedEntity。
但当退出里面的作用域时就是它被摧毁的时候,所以这个weak_ptr现在指向一个无效的Entity。