C++智能指针梳理
来源:互联网 发布:淘宝怎样才能卖耐克 编辑:程序博客网 时间:2024/05/18 01:48
C++智能指针梳理
参考:
- https://en.wikipedia.org/wiki/Memory_leak (维基百科,内存泄漏)
- https://en.wikipedia.org/wiki/Resource_leak (维基百科,资源泄漏)
- http://blog.csdn.net/dangercheng/article/details/12618161(内存泄露和野指针的概念)
- http://blog.csdn.net/na_he/article/details/7429171 (内存泄漏以及常见的解决方法)
- http://blog.csdn.net/skiing_886/article/details/7937907 (C++中为什么需要智能指针)
- http://www.codeproject.com/Articles/541067/Cplusplus-Smart-Pointers (C++11 Smart Pointers)
- http://www.umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf
- http://blog.csdn.net/pi9nc/article/details/12227887 (C++11智能指针之unique_ptr)
- http://blog.csdn.net/u013696062/article/details/39665247 (C++之shared_ptr总结)
- http://blog.csdn.net/yang_lang/article/details/6725041 (C++括号()操作符的重载)
- http://blog.csdn.net/mmzsyx/article/details/8090849 (智能指针 weak_ptr)
- 《C++ Primer 第四版13.5.1节 定义智能指针类》
一、 为何需要智能指针
在C/C++指针引发的错误中有如下两种:内存泄漏和指针悬挂。使用智能指针可以较好地解决这两个问题。
1.1 内存泄漏
内存泄漏的含义可以由以下几个解释中获知:
解释1:In computer science, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in such a way that memory which is no longer needed is not released.
解释2:指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。
解释3:用动态存储分配函数(如malloc)动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。
综合以上的解释,可以看到理解内存泄漏的关键点是“不再使用的内存没有得到释放”。
注意,内存泄漏是指堆内存泄漏(Heap leak)。
补充:关于资源泄漏
In computer science, a resource leak is a particular type of resource consumption by a computer program where the program does not release resources it has acquired. This condition is normally the result of a bug in a program. Typical resource leaks include memory leak and handle leak, particularly file handle leaks.
可见内存泄漏是资源泄漏中的一种,资源泄漏的另外一种是句柄泄漏,例如文件读写,socket操作等都有可能导致句柄的泄漏。
1.2 悬挂指针
悬挂指针也叫野指针,是未初始化或未清零的指针。与空指针(NULL)不同,悬挂指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。
悬挂指针的成因主要有两种:
- 指针变量没有被初始化
任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。 - 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
1.3 实例
1) 忘记释放导致内存泄漏
/* * 忘记释放导致内存泄漏 * * 析构函数未得到执行 */#include <iostream>using namespace std;class Test{public: Test(int a = 0 ) : m_a(a) {} ~Test( ) { cout<<"Calling destructor"<<endl; }public: int m_a;};int main(){ Test *t1 = new Test(3); cout << t1->m_a << endl;// delete t1; return 0;}
2) 异常导致内存泄漏
/* * 异常导致内存泄漏 * * 析构函数未得到执行 */#include <iostream>using namespace std;class Test{public: Test(int a = 0 ) : m_a(a) {} ~Test( ) { cout<<"Calling destructor"<<endl; }public: int m_a;};int main(){ try { Test *t1 = new Test(3); cout << t1->m_a << endl; throw("an exception"); delete t1; } catch(...) { cout << "Something has gone wrong" << endl; } return 0;}
3) 悬挂指针
/* * 浅copy导致的指针悬挂问题 * * 输出类似如下: * destructor ---> Book * destructor ---> 葺葺葺葺 * */#include <iostream>using namespace std;class HasPtr{public: HasPtr(char *s); ~HasPtr();private: char *ptr;};HasPtr::HasPtr(char *s){ if (s == nullptr) { ptr = new char[1]; *ptr = '\0'; } else { ptr = new char[strlen(s) +1]; strcpy(ptr, s); }}HasPtr::~HasPtr(){ cout << "destructor ---> " << ptr << endl; delete ptr;}int main(){ HasPtr p1("Book"); HasPtr p2("Music"); p2 = p1; return 0;}
二、 自定义智能指针类
首先通过引入使用计数类来实现自己的智能指针类,以此加深对智能指针的理解,并解决上面例子中悬挂指针问题。
这部分具体参考《C++ Primer 第四版13.5.1节 定义智能指针类》,代码如下:
/* *自定义智能指针类修复-->浅copy导致的指针悬挂问题 * * 定义计数类:U_ptr * * 智能指针类中需要: * 构造函数、析构函数、copy构造函数、赋值运算符"="重载 * * 输出: * destructor ---> Book * */#include <iostream>using namespace std;class HasPtr;class U_ptr{private: friend class HasPtr; U_ptr(char *s) : use(1) { if (s == nullptr) { sptr = new char[1]; sptr = '\0'; } else { sptr = new char[strlen(s) +1]; strcpy(sptr, s); } } ~U_ptr() { delete sptr; } char *sptr; int use;};class HasPtr{public: HasPtr(char *s); HasPtr(const HasPtr &other); HasPtr &operator=(const HasPtr &other); ~HasPtr();private: U_ptr *ptr;};HasPtr::HasPtr(char *s){ ptr = new U_ptr(s);}HasPtr::~HasPtr(){ if (--ptr->use == 0) { cout << "destructor ---> " << ptr->sptr << endl; delete ptr; }}HasPtr::HasPtr(const HasPtr &other){ ptr->sptr = other.ptr->sptr; ptr->use ++;}HasPtr &HasPtr::operator=(const HasPtr &other){ ++ other.ptr->use; if (-- ptr->use == 0) { delete ptr; } ptr = other.ptr; return *this;}int main(){ HasPtr p1("Book"); HasPtr p2("Music"); p2 = p1; return 0;}
三、 auto_ptr
auto_ptr是一个模版类,是最早出现的智能指针,在C++98标准中已经存在。
智能指针的原理都是RAII(Resource Acquisition Is Initialization),即在构造的时候获取资源,在析构的时候释放资源。
另外,为了使用智能指针,需要引入头文件 #include <memory>
3.1 使用auto_ptr
首先使用它来解决1.3节中实例的问题。
1) 使用auto_ptr修复–>内存泄漏问题
/* * 使用auto_ptr修复-->内存泄漏 * * 析构函数可以执行到 */#include <iostream>#include <memory>using namespace std;class Test{public: Test(int a = 0 ) : m_a(a) {} ~Test( ) { cout<<"Calling destructor"<<endl; }public: int m_a;};int main(){ auto_ptr<Test> t1(new Test(3)); cout << t1->m_a <<endl; return 0;}
2) 使用auto_ptr修复–>浅copy导致的指针悬挂问题
/* * 使用auto_ptr修复-->使用auto_ptr修复-->浅copy导致的指针悬挂问题 * * 输出: * destructor ---> Book * */#include <iostream>#include <memory>using namespace std;class HasPtr{public: HasPtr(char *s); ~HasPtr();private: char *ptr;};HasPtr::HasPtr(char *s){ if (s == nullptr) { ptr = new char[1]; *ptr = '\0'; } else { ptr = new char[strlen(s) +1]; strcpy(ptr, s); }}HasPtr::~HasPtr(){ cout << "destructor ---> " << ptr << endl; delete ptr;}int main(){ auto_ptr<HasPtr>p1(new HasPtr("Book")); auto_ptr<HasPtr>p2(p1); // 所有权转移到p2,p1变为empty return 0;}
3.2 auto_ptr的问题
1) 所有权转移(ownership transfer)
auto_ptr transfers the ownership when it is assigned to another auto_ptr. This is really an issue while passing the auto_ptr between the functions. Say, I have an auto_ptr in Foo( ) and this pointer is passed another function say Fun( ) from Foo. Now once Fun( ) completes its execution, the ownership is not returned back to Foo.
为了说明ownership transfer,参加如下代码
/* * auto_ptr’s ownership transfer * * 该程序运行会崩溃 * 执行完Fun(t1),t1变成empty */#include <iostream>#include <memory>using namespace std;class Test{public: Test(int a = 0 ) : m_a(a) {} ~Test( ) { cout<<"Calling destructor"<<endl; }public: int m_a;};void Fun(auto_ptr<Test> p1 ){ cout << p1->m_a << endl;}int main(){ auto_ptr<Test> t1(new Test(3)); Fun(t1); cout << t1->m_a <<endl; return 0;}
调试显示执行完Fun(t1)的情况:
2) 多个auto_ptr不能同时拥有同一个对象
//error codeTest *t1 = new Test(3);auto_ptr<Test> ptr1(t1);auto_ptr<Test> ptr2(t1);
这里ptr1与ptr2都认为指针t1是归它管的,在析构时都试图删除t1,这样就造成了重复释放问题。程序中释放已经不属于自己的空间,而是非常危险的,比起内存泄露,重复释放是一个更加严重的问题。有可能你第二次释放的空间已经被别的程序所使用,所以C/C++中视这种错误为致命错误,也就是说,我容许你局部的浪费,但绝对不容许你释放(使用)别人的东西。
3) 不能用auto_ptr管理数组指针
因为auto_ptr的析构函数中删除指针用的是delete,而不是delete [],所以我们不应该用auto_ptr来管理一个数组指针。如下的代码虽然可以编译通过,但是不要这样使用。
//bad codeauto_ptr<Test> p(new Test[5]);
4) auto_ptr不可做为容器(vector, list, map)元素
//error codevector<auto_ptr<int> > vec;auto_ptr<int> ptr1(new int(3));vec.push_back(ptr1);
四、 unique_ptr
从第三节的内容可以看出auto_ptr具有比较多的缺陷,使用时容易出错。在C++ 11标准中出现了新的智能指针unique_ptr、 shared_ptr与weak_ptr等,这里首先介绍unique_ptr,可以将unique_ptr看成是auto_ptr的升级替代品。
4.1 基本操作
unique_ptr类中有get()、reset()、release()等函数。
get(): 获得原生指针
reset():重置,显式释放资源
reset(new XX):重置,重新指定对象
release():释放所有权到某一原生指针上
另外可以通过std::move将所有权由一个unique_ptr对象转移到另一个unique_ptr对象上,具体见下例。
#include <iostream>#include <memory>using namespace std;int main(){ //1. unique_ptr的创建 //1.1)创建空的,然后利用reset指定对象 unique_ptr<int> up1; up1.reset(new int(3)); //1.2)通过构造函数在创建时指定动态对象 unique_ptr<int> up2(new int(4)); //2. 获得原生指针(Getting raw pointer ) int* p = up1.get(); //3.所有权的变化 //3.1)释放所有权,执行后变为empty int *p1 = up1.release(); //3.2)转移所有权,执行后变为empty unique_ptr<int> up3 = std::move(up2); //4.显式释放资源 up3.reset(); return 0;}
4.2 禁止赋值和复制
unique_ptr禁止赋值和复制,“唯一”地拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象。也就是说模板类unique_ptr的copy构造函数以及等号(“=”)操作符是无法使用的。
通过禁止复制和赋值可以较好的改善auto_ptr的所有权转移问题。下面是其禁止复制和赋值的例子:
#include <iostream>#include <memory>using namespace std;void Fun1( unique_ptr<int> up ){}int main(){ unique_ptr<int> up1 = unique_ptr<int>(new int(10)); //不允许复制(Copy construction is not allowed),所以以下三个均错误 unique_ptr<int> up2 = up1; // error unique_ptr<int> up3(up1); // error Fun1(up1); // error //不允许赋值('='),所以下面错误 unique_ptr<int> up4; up4 = up1; // error return 0;}
4.3 针对auto_ptr缺陷的改善
1) 管理数组指针
因为unique_ptr有unique_ptr< X[ ] >重载版本,销毁动态对象时调用delete[],所以可以用unique_ptr来管理数组指针。
unique_ptr< Test[ ] > uptr1(new Test[3]);//注意 unique_ptr<Test> uptr3(new Test[3]);是不对的unique_ptr<int[]> uptr2(new int[5]);
2) 做容器(vector, list, map)元素
vector<unique_ptr<int> > vec;unique_ptr<int> ptr1(new int(3));vec.push_back(std::move(ptr1));//vec.push_back(ptr1); //由于禁止复制这样不行
五、 shared_ptr
shared_ptr has the notion called shared ownership. The goal of shared_ptr is very simple: Multiple shared pointers can refer to a single object and when the last shared pointer goes out of scope, memory is released automatically.
从上面这段英文可以看出,shared_ptr是共享所有权的,其内部有一个计数机制,类似于第二节中我们自定义的智能指针类。
5.1 使用shared_ptr
shared_ptr类中有get()、reset()、unique()、swap()等函数。
get(): 获得原生指针
reset():重置,显式释放资源
reset(new XX):重新指定对象
unique ():检测对象管理者是否只有一个shared_ptr实例
在shared_ptr的RAII实现机制中,默认使用delete实现资源释放,也可以定义自己的函数来释放,比如当其管理数组指针时,需要delete[],这时就需要自定义释放函数。自定义释放的方法有两种:lambda表达式和括号操作符的重载。
另外可以通过dynamic_pointer_cast实现继承中的转换,具体见下例。
#include <memory>#include <iostream>using namespace std;class Dealloc{public: Dealloc() {} //括号()操作符的重载 void operator() (int* p ) { if (p != nullptr) { //Do the custom deallocation job cout << "Dealloc called to release the resource " << p << " whose value is " << *p<<endl; delete p; p = nullptr; } }};class Base{public: Base() {} // 虚函数保证Base的多态性,以便在dynamic_pointer_cast中使用 virtual void Foo() {}};class Derived : public Base{public: Derived() {}};int main(){ //1. 创建 shared_ptr<int> sp1 = shared_ptr<int>(new int(100)); shared_ptr<int> sp2 = make_shared<int>(int(10)); auto sp3 = shared_ptr<int>(nullptr); if( sp3 == nullptr ) { cout<<"Null pointer" << endl; } //2. 自定义资源释放函数 { // lamdba表达式 auto sp4 = shared_ptr<int>(new int[5], [ ](int* p){ cout<<"In lambda releasing array of objects..."<<endl; delete[ ] p;}); } { // 括号()操作符的重载 auto sp5 = shared_ptr<int>(new int(1000), Dealloc() ); } //3. 复制 auto sp6(sp1); auto sp7 = sp1; //4. Getting raw pointer int* pRaw = sp2.get( ); //5. Get how many shared pointers sharing the resource long nCount1 = sp1.use_count(); long nCount2 = sp2.use_count(); //6. Is this only shared pointer sharing the resource bool b1 = sp1.unique(); bool b2 = sp2.unique(); //7. swap sp1.swap(sp2); //8. reset sp1.reset(); sp1.reset(new int(20)); //9.Using dynamic_cast_pointer on shared pointer auto sp10 = shared_ptr<Derived>(new Derived( )); shared_ptr<Base> sp11 = dynamic_pointer_cast<Base>(sp10); if (sp11.get( ) != nullptr ) { cout << "Dynamic casting from sp10 to sp11 succeeds...." << endl; } auto sp12 = shared_ptr<Base>(new Base()); shared_ptr<Derived> sp13 = dynamic_pointer_cast<Derived>(sp12); if (sp13 != nullptr) { cout << "Dynamic casting from 12 to 13 succeeds...." << endl; } else { cout << "Dynamic casting from sp12 to sp13 failed ...." << endl; } return 0;}
另外,shared_ptr对象的引用是强引用(stong_ref),如下图:
5.2 shared_ptr的三个问题
1) 多个独立的shared_ptr实例不能共享一个对象
类似于3.2节中auto_ptr的问题
int* p = new int;shared_ptr<int> sptr1( p);//rightshared_ptr<int> sptr2(sptr1); //wrong//shared_ptr<int> sptr2(p);
2) 循环引用问题
循环引用例子:
/* * shared_ptr的循环引用(Cyclic Reference)问题 * * 类A,B的析构函数没有得到执行 * 资源没得到释放 */#include <memory>#include <iostream>using namespace std;class B;class A{public: A( ) : m_sptrB(nullptr) { }; ~A( ) { cout<<" A is destroyed"<<endl; } shared_ptr<B> m_sptrB;};class B{public: B( ) : m_sptrA(nullptr) { }; ~B( ) { cout<<" B is destroyed"<<endl; } shared_ptr<A> m_sptrA;};int main( ){ shared_ptr<B> sptrB( new B ); shared_ptr<A> sptrA( new A ); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; return 0;}
过程分析:
为了解决shared_ptr的循环引用问题,需要用到weak_ptr
3) shared_ptr的this问题
假设如下情况,类Thing的一个成员函数需要传递其this指针到一个普通函数,如果不用智能指针,没有问题,如下:
#include <iostream>using namespace std;class Thing;void UseThingThisPointer(Thing *);class Thing {public: void foo() {//注意这里this UseThingThisPointer(this); } void print() { cout << "class Thing" << endl; }};void UseThingThisPointer(Thing * ptr){ ptr->print();}int main(){ Thing * t1 = new Thing; t1->foo(); delete t1;}
此时,我们需要使用shared_ptr智能指针来自动地管理Thing内存,如何使用呢?若我们简单的认为将所有的Thing *用shared_ptr替换,代码如下,这时虽然可以编译通过,但是代码存在较大问题。
//...class Thing {public: void foo() { //注意这里this shared_ptr<Thing> sp_for_this(this); // danger! a second manager object! UseThingThisPointer(sp_for_this); } //...};void UseThingThisPointer(shared_ptr<Thing> ptr){ ptr->print();}int main(){ shared_ptr<Thing> t1(new Thing); t1->foo(); return 0;}
通过调试可以发现,在执行shared_ptr t1(new Thing)后创建了类Thing的一个强引用(管理对象),在foo函数中使用shared_ptr sp_for_this(this)也在原生指针上创建了一个强用(管理对象),这样共有两个shared_ptr管理同一个对象。但是在foo()执行完成时,sp_for_this超出范围,其管理的Thing对象会被释放,留下t1指向一个不存在的对象。最后程序运行结束,释放t1管理的对象时便出现问题。此现象引发的问题和shared_ptr的第一个问题(如下代码)一样。
int* p = new int;shared_ptr<int> sptr1( p);shared_ptr<int> sptr2( p );
为了解决shared_ptr的this问题问题,也需要用到weak_ptr。
六、 weak_Ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它没有重载operator*和->,故而不具有普通指针的行为。它的最大作用在于协助shared_ptr工作。
weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
6.1 使用weak_ptr
weak_ptr类中有use_count()、expired()等函数。
use_count():观测资源的引用计数
expired():等价于use_count()==0,但更快
lock():获取shared_ptr,当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr
具体见下例
/* * weak_ptr的使用 */#include <iostream>#include <memory>using namespace std;class Test{public: Test(int a = 0) : m_a(a) { } ~Test( ) { cout<<"Test object is destroyed whose value is = "<<m_a<<endl; }public: int m_a;};int main( ){ //1. Create a shared pointer shared_ptr<Test> sp1(new Test(5) ); //2. Create a weak pointer from the shared pointer weak_ptr<Test> wp1 = sp1;//(sp1);// = sp1; //3. Create a weak pointer from another weak pointer weak_ptr<Test> wp2(wp1);// = wp1; //4. Get the reference count of the shared pointer int nShared = sp1.use_count(); int nWeak = wp1.use_count(); //5.lock shared_ptr<Test> sp2 = wp1.lock(); nShared = sp1.use_count(); nWeak = wp1.use_count(); //6. expired() { shared_ptr<Test> sp3 = shared_ptr<Test> (new Test(100)); wp1 = sp3; } if (wp1.expired()) { cout << "expired" << endl; } return 0;}
weak_ptr对象的引用是弱引用(weak ref),如下图:
6.2 解决shared_ptr的问题
1) 解决shared_ptr循环引用问题
/* * 使用weak_ptr修复-->shared¬_ptr循环引用问题 * * 析构函数可以执行到 * */#include <iostream>#include <memory>using namespace std;class B;class A{public: A( ) : m_a(5) { }; ~A( ) { cout<<"A is destroyed"<<endl; } void PrintSpB( ); weak_ptr<B> m_sptrB; int m_a;};class B{public: B( ) : m_b(10) { }; ~B( ) { cout<<"B is destroyed"<<endl; } weak_ptr<A> m_sptrA; int m_b;};void A::PrintSpB( ){ if( !m_sptrB.expired() ) { cout<< m_sptrB.lock( )->m_b<<endl; }}int main( ){ shared_ptr<B> sptrB(new B ); shared_ptr<A> sptrA(new A ); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; sptrA->PrintSpB( ); return 0;}
2) 解决shared_ptr的this问题
/* * 使用weak_ptr修复--> shared_ptr的this问题 * * 1) 继承模板类enable_shared_from_this * 2) 用shared_from_this()替换this* */#include <iostream>#include <memory>using namespace std;class Thing;void UseThingThisPointer(shared_ptr<Thing> ptr);class Thing : public enable_shared_from_this<Thing>{public: void foo() { // get a shared_ptr from the weak_ptr in this object UseThingThisPointer(shared_from_this()); } void print() { cout << "class Thing" << endl; }};void UseThingThisPointer(shared_ptr<Thing> ptr){ ptr->print();}int main(){ shared_ptr<Thing> t1(new Thing); t1->foo(); return 0;}
- C++智能指针梳理
- C++智能指针梳理
- c++:智能指针
- C++:智能指针
- 【C++】智能指针
- 【C++】智能指针
- C++PJ智能指针
- 【c++】智能指针
- 【C++】智能指针auto_ptr
- C++,智能指针
- C++,boost智能指针
- 智能指针模拟C
- [C++]智能指针
- 【C++】 浅析智能指针
- C++_智能指针
- C++:初识智能指针
- C++::智能指针
- 【C++】智能指针
- LeetCode 25 Reverse Nodes in k-Group
- EventBus 的使用
- Fragment + viewpager + Fragment 二次进入不显示
- 六招帮你提高网页的加载速度 改善用户体验
- chrome console控制台引入jquery库
- C++智能指针梳理
- Cookie,Session和Cache的区别
- android Settings 修改默认主题为白底黑字
- android levellistdrawable 基本使用
- Ubuntu14.04下安装搜狗输入法
- CentOS7安装Docker,运行Nginx镜像、Centos镜像
- javaCV开发详解之7:让音频转换更加简单,实现通用音频编码格式转换、重采样等音频参数的转换功能(以pcm16le编码的wav转mp3为例)
- 怎么说都是自己的第一篇文章
- 百度地图error inflating class com.baidu.mapapi.map.MapView