C++11智能指针
本文将讨论C++ 11 中的智能指针,如何使用以及注意事项。
share_ptr
shared_ptr是c ++ 11提供的一种Smart Pointer类,它能够在不再被引用的时候自动释放内存,从而帮助我们消除内存泄漏和指针悬空的问题。shared_ptr 对象内部指向两个内存,分别是目标对象的内存以及一个引用计数器。当shared_ptr关联到一个指针时,它会让引用计数器加1,当shared_ptr对象离开作用域时,将调用析构函数,在析构函数内部,它会将引用计数减1,如果引用计数的新值为0,则它将删除关联的原始指针。
-
创建shared_ptr对象并初始化
shared_ptr<int> s1(new(int));
-
创建shared_ptr 对象当暂时未设置目标指针
shared_ptr<int> s3 = make_shared<int>(); //此时值为nullptr,引用计数器为1
-
查看shared_ptr对象的引用计数器
int count = s1.use_count();
-
重置shared_ptr
s2.reset(); //引用计数减1,如果引用计数变为0,则删除指针,否则值变为nullptr。 s2.reset(new int(1)); //相当于reset()之后再重新赋值 s2 = nullptr; //也可以这样
-
普通使用
shared_ptr是伪指针,我们可以将*和->与shared_ptr对象一起使用,也可以像其他shared_ptr对象一样对其进行比较;
shared_ptr<int> s1(new int(111)); shared_ptr<int> s2 = s1; if(s1==s2){ s2 = nullptr; } *s2 = 99; cout << *s1 << endl; cout << s1.use_count() << " " << s2.use_count() << endl; if(!s2){ cout<<"s2 is null"; }
-
进阶:自定义释放内存方式
shared_ptr 默认使用
delete
方式来释放指向的对象,但是若该对象是数组,则析构函数不能正常完成工作。此时需要为shared_ptr提供一个自定义的析构函数。注意模板参数里使用int 而非int*//方法1:Lambda表达式 shared_ptr<int>(new int[1000], [](int* i){delete[] i; }); //方法2:函数指针 void deleter(int *s){ delete[] s; } shared_ptr<int>(new int[1000], &deleter);
-
细节&注意事项
shared_ptr的主要优点是,当不再使用它时,它将自动释放关联的内存。但是,如果我们不小心使用shared_ptr,那么这种优势就会变成劣势。
-
不可以使用以下方式声明
shared_ptr<int> s1 = new int(); //编译错误,
-
shared_ptr没有提仅提供了->, * ,以及比较运算符,没有+、++、[]等运算符。
-
可以通过get函数来访问shared_ptr内部的原始指针,但是不推荐使用这样的方式,因为可能导致访问已释放内存的问题。
shared_ptr<int> s1(new int[10], deleter); int *s2 = s1.get();
-
不要使用裸指针为智能指针赋值,这会导致删除已释放的内存,引发程序崩溃。
int *rawPtr = new int(); shared_ptr<int> ptr1(rawPtr); shared_ptr<int> ptr2(rawPtr);
-
不要关联到栈内存,否则该内存会被释放两次,引发程序崩溃。
void demo(){ int i = 999; shared_ptr<int>ptr(&i); return; }
-
shared_ptr 循环引用会导致内存泄漏,例如一条环状链表,在离开作用域后没有节点会被释放。解决这个问题可以使用weak_ptr。
struct Node{ string name; public: shared_ptr<Node> next; Node(string n){ name = n; } ~Node(){ cout << name << " have been delete...\n"; } }; void demo(){ shared_ptr<Node> n1(new Node("n1")); shared_ptr<Node>n2(new Node("n2")); n2->next = n1; //n1和n2为一条链 shared_ptr<Node> n3(new Node("n3")); shared_ptr<Node>n4(new Node("n4")); n3->next = n4; n4->next = n3; //n3和n4形成了环 } int main(){ demo(); //只用n1、n2被释放 return 0; }
-
weak_ptr
weak_ptr 允许共享但不拥有对象,它的对象是由shared_ptr创建的。使用weak_ptr对象,我们不能直接使用运算符*和->来访问关联的内存,必须通过调用weak_ptr对象的lock()函数来创建一个shared_ptr,然后才能使用它。
-
创建示例
shared_ptr<int> share = make_shared<int>(100); weak_ptr<int> weak(share);
-
使用示例
weak_ptr 创建时使shared_ptr 的弱引用增加1,弱引用计数不会妨碍shared_ptr 资源的释放。weak_ptr 需要通过expired()方 法判断资源是否仍然有效以及lock()方法使shared_ptr强引用+1,然后才能使用指向的资源,若shared_ptr已经释放,lock()将返回一个空的shared_ptr。这样的特性可以解决循环引用时shared_ptr不能正常释放资源的问题。
void demo2(){ shared_ptr<int> shareP = make_shared<int>(100); weak_ptr<int> weakP(shareP); auto tmp = weakP.lock(); cout << "use_count is " << shareP.use_count() << endl; if (tmp){ cout << *tmp << endl; tmp.reset(); } cout << "use_count is " << shareP.use_count() << endl; if (!weakP.expired()){ cout << "weap_ptr not expired..." << endl; } shareP.reset(); if (weakP.expired()){ cout << "weap_ptr expired..." << endl; } }
-
使用weak_ptr解决之前内存泄漏的问题
将Node结构体中的next类型又shared_ptr改成weak_ptr即可。
unique_ptr
unique_ptr 始终是关联的原始指针的唯一所有者,我们不能复制unique_ptr,只能移动。当unique_ptr离开作用域或资源被其它资源重写了,则会自动释放之前的资源。所以它保证了他所关联的资源总是能被释放。
-
创建对象
unique_ptr 接受原始指针作为参数。并且已经删除了赋值运算符。
unique_ptr<int> p(new int(100)); unique_ptr<int> p2 = new int(100); //编译错误
-
关于重置
unique_ptr 调用reset()函数将对其进行重置,即它将删除关联的原始指针并使对象为空。
unique_ptr<int> p(new int(100)); p.reset(); if (p == nullptr){ cout << "Null" << endl; }
-
关于复制
unique_ptr 是不可复制的,只能移动。因此,无论是通过拷贝构造函数还是赋值运算符,我们都无法创建unique_ptr对象的拷贝。
unique_ptr<int> p1(new int(100)); unique_ptr<int> p2 = p1; //编译错误 unique_ptr<int> p3(p1); //编译错误
-
转移对象的所有权
unique_ptr不能复制,只能移动。这意味着unique_ptr对象可以将关联的原始指针的所有者转移到另一个unique_ptr对象。
unique_ptr<int> p1(new int(100)); unique_ptr<int> p2 = move(p1); if (p1 == nullptr){ cout << "P1 is null...\n"; }
-
释放原始指针
在unique_ptr对象上调用release()将释放该对象的关联原始指针的所有权。
它返回原始指针。注意release仅是释放所有权,不会释放原始指针的内存。unique_ptr<int> p1(new int(100)); int *val = p1.release(); cout << *val << endl; //100 if (p1 == nullptr){ cout << "p1 is null" << endl; }