前言
使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。所以智能指针主要解决内存泄漏的问题,使用智能指针可以自动释放。
C++里面的四个智能指针: auto_ptr,unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。
1.shared_ptr共享对象的所有权,但性能略差。
2.unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。
3.weak_ptr配合shared_ptr,解决循环引用的问题。
shared_ptr
shared_ptr 内部包含两个指针,一个指向对象
,另一个指向控制块
(control block),控制块中包含一个引用计数
(reference count), 一个弱计数
(weak count)和其它
一些数据。
shared_ptr使用引用计数
,每一个shared_ptr的拷贝都指向相同的内存
。再最后一个shared_ptr析构的时候,内存才会被释放,也就是引用计数为0
了。
shared_ptr 的基本用法和常用函数
创建shared_ptr智能指针
应该优先使用make_shared来构造智能指针,因为它更高效。
std::shared_ptr构造函数会执行两次
内存申请,而std::make_shared则执行一次
。
shared_ptr在实现的时候使用的refcount技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr<A> p2(new A)的时候,首先会申请数据的内存
,然后申请内控制块
,因此是两次内存申请,而std::make_shared<A>()则是只执行一次内存申请,将数据和控制块的申请放到一起。(因为make_shared只申请一次内存,因此控制块和数据块在一起,只有当控制块中不再使用时,内存才会释放,但是weak_ptr却使得控制块一直在使用,只要有weak_ptr没有析构就不会释放这块空间。(理解这个需要继续往下看))
注意:不能将一个原始指针直接赋值给一个智能指针。
shared_ptr<int> sp1 = make_shared<int>(100);
shared_ptr<int> sp1(new int(100));
shared_ptr<int> p = new int(1);//err
辅助函数
//返回shared_ptr中保存的裸指针
s.get()
//重置shared_ptr
s.reset(…)
//返回shared_ptr的强引用计数
s.use_count()
//若use_count()为1,返回true,否则返回false
s.unique()
reset():
reset( )不带参数时,引用计数减少1,同时将智能指针置空(也就是指向为NULL)。
reset( )带参数时,减少之前内存对象的引用计数,指向新的对象。
若这个智能指针是唯一指向new对象的指针(reset后计数为0了),则释放new对象。
注意:谨慎使用.get()获得的原始指针,小心shared_ptr指针析构的时候delete一个已经释放的new对象就会报错。
删除器
shared_prt是可以指定删除器的,当指针的引用计数为0时,自动调用删除器来释放对象的内存。删除器可以是普通函数指针或lambda表达式。
当指定删除器时,使用删除器。不指定删除器时,普通的delete指针。
那么数组的delete就需要我们来指定删除器了。
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
//普通函数指针
std::shared_ptr<int> p(new int(1), DeleteIntPtr);
// lambda表达式
std::shared_ptr<int> p2(new int(1), [](int *p) {
cout << "call lambda1 delete p" << endl;
delete p;
});
传参传的是new出来的对象类型指针。
shared_ptr注意事项
不要用一个原始指针初始化多个shared_ptr
int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);
这样同一块空间会重释放。
不要在函数实参中创建shared_ptr
因为C++允许参数在计算的时候打乱顺序,C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右。
void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}
f(std::shared_ptr<Lhs>(new Lhs()),
std::shared_ptr<Rhs>(new Rhs())
);
可能的顺序如下:
1.new Lhs()
2.new Rhs()
3.std::shared_ptr
4.std::shared_ptr
此时假设第2步出现异常,则在第一步申请的内存将没处释放了,上面产生内存泄露
的本质是当申请数据指针后,没有马上传给std::shared_ptr。
通过shared_from_this()返回this指针
class A {
public:
shared_ptr<A> GetSelf() {
return shared_ptr<A>(this); // 不要这么做
}
};
int main() {
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf();
return 0;
}
这个是肯定报错的,this为该对象的首地址其实就已经违反“不要用一个原始指针初始化多个shared_ptr”。
那怎么才能实现这类操作呢?
让目标类通过继承std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()来返回this的shared_ptr
那么该类就会被两个智能指针共享,并且只会析构一次。
避免循环引用
循环引用会导致内存泄漏,循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生内存泄漏。
class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main() {
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout << "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
这个问题将在讲解完weak_ptr之后迎刃而解。
unique_ptr
unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再拥有
原来指针的所有权了。
注意:std::make_shared是c++11的一部分,但std::make_unique不是。它是在c++14里加入标准库的。
unique_ptr和shared_ptr的一些区别
unique_ptr可以指向一个数组,代码如下所示:
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的
unique_ptr需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器:
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;});
weak_ptr
介绍
简单理解就是shared_ptr很好还不够最好,这个weak_ptr是用来辅佐shared_ptr的。
weak_ptr 是一种不控制对象生命周期的智能指针
, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段
。
它是对对象的一种弱引用
,不会
增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock()函数来获得shared_ptr。weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源
,主要是为了通过shared_ptr获得资源的监测权,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在。
构造智能指针weak_ptr
它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造.
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
wp成员函数
use_count():
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果输出1,获取当前观察资源的引用计数
expired():
用来判断所观察资源是否已经释放,返回值为bool。
lock():
lock()返回shared_ptr,也就是返回监测的shared_ptr。当shared_ptr在引用计数为0的时候,lock()之后返回给的新的shared_ptr也只能指向空。
int main()
{
weak_ptr<int>weakptr;
{
shared_ptr<int> ptr(new int(100));
weakptr = ptr;
shared_ptr<int>cur_ptr = weakptr.lock();
if (cur_ptr == NULL) {
cout << "ptr is null" << endl;
cout << cur_ptr.use_count() << endl;
}
else {
cout << "ptr is not null" << endl;
cout << cur_ptr.use_count() << endl;
}
}
shared_ptr<int>ptr1 = weakptr.lock();
if (ptr1 == NULL) {
cout << "ptr is null" << endl;
cout << ptr1.use_count() << endl;
}
return 0;
}
通过weak_ptr返回this指针
上文shared_ptr中提到不能直接将this指针返回shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回指针,原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr返回。
weak_ptr解决循环引用问题
在上文shared_ptr提到智能指针循环引用的问题,因为智能指针的循环引用会导致内存泄漏,可以通过weak_ptr解决该问题,只要将A或B的任意一个成员变量改为weak_ptr即可。
可以简单理解为一个改为weak_ptr之后,当另一个离开作用域后就不会受到限制就把自己内部限制对方的也释放掉,因而对方释放的时候得而释放。
使用weak_ptr要注意的问题
在使用wp前需要调用wp.expired()函数判断一下。
虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息,直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。
我们在使用weak_ptr之前需要先lock,再判断。因为考虑到多线程的情况,当你在wp.expired()函数判断有的时候,另一个线程把计数玩成0了,你这接下来可就是违规操作了。lock可以先多加一个计数,就算另一个线程玩的减1了,你这边还有计数拉着它不会让那块被释放。
使用和不使用make_shared 相比图
不使用:
使用: