C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己
管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的
概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常
时内存泄露等问题等,使用智能指针能更好的管理堆内存。
C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,
并且第一个已经被C++11弃用。
智能指针的思想是通过一个对象去管理另一个对象的方式,在函数退栈的时候由于栈上的内存都会被释放,释放对象的时候就会调用该对象的析构函数。 智能指针就是一个管理别的对象的类,在它析构的时候如果条件符合那么就会自动它管理的对象也进行析构调。 同时重载指针的操作运算符实现了指针的操作。
std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析
构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个
shared_ptr对象销毁时,被管理对象自动销毁。
简单来说,shared_ptr实现包含了两部分
首先先说下头文件智能指针的头文件 #include
#include
#include
using namespace std;
class A{
public:
A(){
std::cout <<"A()" << endl;
}
~A(){
std::cout <<"~A()" <<endl;
}
};
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
void shard_init()
{
// std::shared_ptr p1 = new int(1); //A compiler error
std::shared_ptr<int> p2(new int(1)); //堆上开辟一个int的空间并赋值为1,交给智能指针保管
std::shared_ptr<int> p3 = p2; //p3和p2同时指向刚刚开辟的空间
std::shared_ptr<int> p4 = make_shared<int>(10); //推荐的初始化方法
std::shared_ptr<int> p5; //裸指针
auto p6 = p5;
auto p7 = make_shared<int>(1000); //这个是最推荐的写法,最简洁
std::shared_ptr<int> p8(new int(10000), DeleteIntPtr); // shared_ptr 可以自定义释放内存的方法,这样一来还可以关闭文件等操作。
std::shared_ptr<int> p9(new int(100000), [](int* p){ //使用匿名函数进行内存释放
cout << "delete p9"<<endl;
delete p;
});
std::shared_ptr<A> p10(new A[10], [](A* p){ //开辟了一个数组,这个数组中放的否是A的指针
cout << "delete p10" <<endl;
delete []p;
});
std::shared_ptr<int> p11(new int[10], [](int* p){
cout << "delete p10" <<endl;
delete []p;
});
p11.get()[10] = 100;
}
总结一下上述的内容:
void shard_operation()
{
auto p1 = make_shared<A>();
//查看被引用的数量
cout << p1.use_count() << endl;
auto p2 = p1;
cout << p1.use_count() << endl;
//释放
p1.reset();
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
//获取裸指针,可以通过智能指针获取到裸指针,但是这是一个很危险的操作,如果不小心释放了
//这个裸指针,那么程序会崩给你看。
A* ptr = p1.get();
// delete ptr;
//获取裸指针的一般是用一些函数的参数是原声指针的情况
// void test(A* ptr);
}
use_count()
可以获取该智能指针有多少引用计数。reset()
可以将该指针的资源引用计数进行减1,这个接口有很多重载的函数,也可以重新设置要管理的资源get()
获取资源的裸指针int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);
如果直接运行会导致两次释放,p1和p2都进行释放,从而double free
function(shared_ptr<int>(new int), g()); //有缺陷
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,如下:
shared_ptr<int> p(new int);
function(p, g());
class B
{
public:
shared_ptr<B> GetSelf()
{
return shared_ptr<B>(this); // 不要这么做
}
~B()
{
cout << "Deconstruction B" << endl;
}
};
class C :public std::enable_shared_from_this<C>
{
public:
shared_ptr<C> GetSelf()
{
return shared_from_this();
}
~C()
{
cout << "Deconstruction B" << endl;
}
};
void shared_ptr_waring(){
shared_ptr<B> sp1(new B);
//shared_ptr sp2 = sp1->GetSelf();
cout << sp1.use_count() << endl;
//cout << sp2.use_count() << endl;
shared_ptr<C> spc1(new C);
shared_ptr<C> spc2 = spc1->GetSelf();
cout << spc1.use_count() << endl; //2
cout << spc1.use_count() << endl; //2
}
首先在
shared_ptr<B> sp1(new B);
shared_ptr<B> sp2 = sp1->GetSelf();
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
如果将注释放开实际上有两个智能指针sp1和sp2指向了同一块内存,这样会导致重复释放。
如果非要返回该功能则需继承enable_shared_from_this
上述代码指针 spc1和spc2就是正常的如果打印出引用的指都是2
class D;
class E;
class D{
public:
~D(){
cout << "D" <<endl;
}
shared_ptr<E> pe;
};
class E{
public:
~E(){
cout << "E" <<endl;
}
shared_ptr<D> pd;
};
void circular_reference()
{
shared_ptr<D> p1(new D);
shared_ptr<E> p2(new E);
p1->pe = p2;
p2->pd = p1;
cout << p1.use_count() <<endl; //打印2
cout << p2.use_count() <<endl; //打印2
}
上述的内容当走出circular_reference
的作用域的时候,被new 出来的D和E并没有释放。
其很好理解,函数体内的p1和p2进行引用减少了但是类中还有成员变量的引用计数,这样就会导致引用计数不会到0,从而不会释放内存。
上述的问题可以使用: ```std::weak_ptr`` 进行处理
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在。weak_ptr还可以返回this指针和解决循环引用的问题。
说人话就是:
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl;
上述的打印结果为1, 说白了就是一个监视者
expired()
方法判断所观察资源是否已经释放shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
打印: weak_ptr有效;
lock()
方法可以获取到监视的shared_ptr,如果被监视的无效,那么将会返回一个空的shared_ptr。std::weak_ptr<int> gw;
void f()
{
if(gw.expired()) {
cout << "gw无效,资源已释放";
}
else {
auto spt = gw.lock();
cout << "gw有效, *spt = " << *spt << endl;
}
}
int main()
{
{
auto sp = atd::make_shared<int>(42);
gw = sp;
f();
}
f();
return 0;
}
之前如果要通过shared_ptr返回this,导致释放两次的问题可以通过继承std::enable_shared_from_this
class D;
class E;
class D{
public:
~D(){
cout << "D" <<endl;
}
std::weak_ptr<E> pe;
// shared_ptr pe;
};
class E{
public:
~E(){
cout << "E" <<endl;
}
shared_ptr<D> pd;
};
void circular_reference()
{
shared_ptr<D> p1(new D);
shared_ptr<E> p2(new E);
p1->pe = p2; //p1->pe : E.pe(weak_ptr)
p2->pd = p1; //p2->pd : shared_ptr
cout << p1.use_count() <<endl; 2
cout << p2.use_count() <<endl; 1
}
打印信息:
2
1
E
D
weak_ptr 不占用引用计数, 所以p2.use_count()
为1。 当析构的时候首先p2所指向的资源被析构,之后p2.pd引用计数会减少1,之后p1析构从而正确释放资源,解决循环引用的问题。
unique_ptr<T> my_ptr(new T);
unique_ptr<T> my_other_ptr = my_ptr;
unique_ptr<T> my_other_ptr = std::move(my_ptr);
第二行会报错。
可以用第三行的方式。
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;});
关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。如果希望只有一个智能指针管
理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。
上面的就是目前三种智能指针的使用方法,后面在实战中用到了如果有什么要注意的后面再更新吧。
不早了 晚安晚安。