在实际的C++开发过程中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用的内存越来越多最终不得不重启等问题,这些问题往往都是内存管理资源不当造成的。比如:
针对以上情况,C++提供了更友好的内存管理机制,让程序员更专注于开发项目的各个功能上,而不是自己进行内存管理。事实上,早在1959年前后,就有人提出“垃圾自动回收”机制,“垃圾”指的是那些不再使用或者没有任何指针指向的内存空间,而“回收”指的是将这些“垃圾”收集起来以便再次利用。
在C++11标准中,增加了nique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收。
智能指针和普通指针用法相似,智能指针的本质是一个模板类,对普通指针进行了封装,通过在构造函数中初始化分配内存,在析构函数中释放内存,达到自己管理内存,不需要手动管理内存的效果,因此智能指针可以在适当时机自动释放分配的内存,即智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题。
接下来对shared_ptr的原理和用法做详细的解释说明。
通过构造函数、std::make_shared和reset初始化三种初始化方式
//1.通过如下两种方式,可以构造出 shared_ptr 类型的空智能指针,对于空的指针,其初始引用计数方式为0,不是1
std::shared_ptr<int> p1; //不传入任何实参
std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr
//2.创建指针时,可以明确指向
std::shared_ptr<int> p3(new int(10)); //指向一个存有10这个int类型数据的堆内存空间
//3.调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr p4 = p3;
//如果P3为空,则P4也为空,其引用计数初始值为0,反之,则表明P4和P3指向同一块堆内存,同时堆内存的引用次数会加1.
//4.调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);
// 即P5拥有了P4的堆内存,而P4则变成了空智能指针
std::make_shared 初始化方式,C++11 标准中提供了 std::make_shared 模板函数,其可以用于初始化 shared_ptr 智能指针
//1.定义一个空的智能指针
std::shared_ptr<int> p6 = std::make_shared<int>();
//2.创建指针,并明确指向
std::shared_ptr<int> p7 = std::make_shared<int>(10);
//3.auto关键字代替std::shared_ptr,p8指向一个动态分配的空vector
auto p8 = make_shared<vector<int>>();
//创建了一个指针,并明确指向
//调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将旧对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。
std::shared_ptr<int> p8 = nullptr;
p8.reset(new int(1));
//当智能指针中有值的时候,调用reset()会使引用计数减1,如果引用计数为0时,则析构旧对象
p8.reset();
智能指针一般都提供了get()成员函数,用来执行显示转换,即返回智能指针内部的原始指针。
std::shared_ptr<int> p9(new int(5));
int *pInt = p9.get();
如果需要调用成员函数,由于几乎所有的智能指针都重载了 * ,->操作符,所以直接使用把智能指针当做一般的指针变量来使用就可以了。但是有时候需要传递参数,如果参数是 T*,那么传递一个智能指针类是无法识别的,因此需要使用原始指针。
①不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:
int *p5 = new int;
std::shared_ptr<int> p6(p5);
std::shared_ptr<int> p7(p5);// logic error
②不要在函数实参中创建shared_ptr。因为C++的函数参数的计算顺序在不同的编译器下是不同的。正确的做法是先创建好,然后再传入。
function(shared_ptr<int>(new int)); // 先创建shared_ptr(new int),避免不同编译器的不同行为
③shared_ptr不支持动态数组,但是本地编译器由通过了?
C++17及以后是支持动态数组的,C++11/14是不支持的,只要是最新的编译器是没问题的。
④避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。
⑤线程安全问题。参考智能指针线程安全问题