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)的情况:
auto_ptr控制权转移

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),如下图:
shared_ptr强引用

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循环引用过程

为了解决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),如下图:
弱引用(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;}
2 0