前言
shared_ptr机制
使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,再最后一个shared_ptr析构的时候,内存才会被释放。
shared_ptr实现
1.一个指向堆上创建的对象裸指针,raw_ptr
2.一个指向内部隐藏的,共享的管理对象,share_count_object.使用use_count()可以知道当前堆上的对象被多少对象引用了,简单来说就是引用计数。
构建shared_ptr方式
1.优先使用make_shared来构造智能指针,因为比较高效。
auto sp1 = make_shared<int>(100);
2.构造函数构造
std::shared_ptr<int> p1(new int(1))
3.对于一个未初始化的智能指针,可以使用reset函数初始化,比如
std::shared_ptr<int> p1;
p1.reset(new int(1));
注意构建shard_ptr注意以下2点
(1).reset()函数如果参数为空时,是将智能指针对象置空,并将智能指针所指的对象计数减一(如果所指的对象有智能指针指向时)。
(2).要注意智能指针并不是一个普通的指针,因此不能将一个原始指针指针赋值给一个智能指针,比如以下这种方法是错误的
std::shard_ptr<int> p = new int(1);
构建方式代码如下:
void shared_ptr_test1(){
qDebug() << "测试构造shared_ptr";
std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3;
p3.reset(new int(1));//给p3赋值
std::shared_ptr<int> p4 = p3;
if(p3){
cout << "p3 is not null" << endl;
}
cout << "p1 use_count:" << p1.use_count() << endl;
cout << "p2 use_count:" << p2.use_count() << endl;
cout << "p3 use_count:" << p3.use_count() << endl;
//只是让p3不指向任何对象,但是实际上原来指向的对象还被p4指向
p3.reset();
cout << "after reset() p3 , p3 use_count:" << p3.use_count() << endl;
cout << "after reset() p3 , p4 use_count:" << p4.use_count() << endl;
p4.reset();//此时p3开始指向的对象已经没有指针指向
cout << "after reset() p4 , p4 use_count:" << p4.use_count() << endl;
}
shared_ptr 操作
1.获取原始指针(非特殊情况下不要使用):get()
注意:该函数返回一个原始的指针值,使用需要遵守以下几个规定
(1)不要保存p.get()的返回值,无论保存为裸指针还是shared_ptr都是错误的。
(2)保存为裸指针不知道什么时候就变成为悬空指针,保存为shared_ptr则产生了独立指针
(3)不要delete p.get()的返回值,会导致对一块内存delete 2次的错误。
2.指定删除器(常用)
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
特别是用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象。
删除器例子程序如下:
static void delete_int_ptr(int *p){
qDebug() << "使用函数作为删除器";
delete p;
}
void shared_ptr_test3(){
qDebug() << "测试shared_ptr删除器";
std::shared_ptr<int> p1(new int(1), delete_int_ptr);
std::shared_ptr<int> p2(new int(1), [](int *p){
qDebug() << "使用lambda作为删除器";
delete p;
});
std::shared_ptr<int> p3(new int[10], [](int *p){
qDebug() << "删除数组";
delete []p;
});
qDebug() << "注意释放顺序:p3,p2,p1-栈是先进后出";
}
使用shared_ptr注意的问题
1.不要用一个原始指针初始化多个shared_ptr,这样会导致double delete,例子程序见shared_ptr_test4。
void shared_ptr_test4(){
qDebug() << "多个智能指针指向同一个指针会导致double delete,QT环境没报错?但是这是一种错误";
int *ptr = new int;
shared_ptr<int> p1(ptr, [](int *p1){
qDebug() << "delete p1:" << p1;
delete p1;
});
shared_ptr<int> p2(ptr, [](int *p2){
qDebug() << "delete p2:" << p2;
delete p2;
}); // 逻辑错误
qDebug() << "p1.get():" << p1.get() << "p2.get():" << p2.get();
//其实指针对象有2个引用,但是计数却是分别计数的,这样就会导致多次内存释放。
qDebug() << "p1.use_cont:" << p1.use_count() << "p2.use_count:" <<p2.use_count();
}
2.不要再函数实参中创建shared_ptr,对于下面的写法
function(shared_ptr(new int), g()); //有缺陷
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也 可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还 没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针。
shared_ptr p(new int);
function(p, g());
3.通过shared_from_this()返回this指针,不要将this指针作为shared_ptr返回回来,因为this指针本质上是一个裸指针,因此这样会导致重复析构,类似1,实际上也是将一个原始指针初始化了多个shared_ptr. 正确做法是让目标类通过std::enable_shared_from_this类,然后使用基类的 成员函数shared_from_this()来返回this的shared_ptr,如下所示。例子参见shared_ptr_test5.
class A{
public:
shared_ptr<A> get_self(){
return shared_ptr<A>(this);//错误做法
}
~A(){
qDebug() << "Deconstruction A";
}
};
class B:public std::enable_shared_from_this<B>{
public:
shared_ptr<B> get_self(){
return shared_from_this();//正确做法
}
~B(){
qDebug() << "Deconstruction B";
}
};
void shared_ptr_test5(){
qDebug() << "测试返回this指针的正确做法和错误做法";
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->get_self();
shared_ptr<B> sp3(new B);
shared_ptr<B> sp4 = sp3->get_self();
}
4.避免循环引用,循环引用会导致内存泄漏(函数完成计数无法清零导致,使用weak_ptr可以解决这个问题),循环引用导致cp和dp的引用计数为2,在离开作用域之后,cp和dp的引用计数减为1,并不回减为0,导 致两个指针都不会被析构,产生内存泄漏。例子程序见shared_ptr_test6
class C;
class D;
class C{
public:
std::shared_ptr<D> dptr;
~C() {
cout << "C is deleted" << endl;
}
};
class D{
public:
std::shared_ptr<C> cptr;
~D() {
cout << "D is deleted" << endl;
}
};
void shared_ptr_test6(){
{
std::shared_ptr<C> cp(new C);
std::shared_ptr<D> dp(new D);
cout << "dp use count:" << dp.use_count() << endl;
cp->dptr = dp;
cout << "dp use count:" << dp.use_count() << endl;
dp->cptr = cp;
}
cout<< "main leave" << endl; // 循环引用导致cp dp退出了作用域都没有析构
}
总结
本博客说明了C++11 中智能指针的部分用法,当然share_ptr还有其他用法,这里只是列举了个人觉得比较重要的部分。资料总结来源于腾讯课程-零声学院-darren老师