C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)

注意:现在boot库已经归入STL库,用法基本上还和boost类似
在C++11中,引入了智能指针。主要有:unique_ptr, shared_ptr, weak_ptr。
这3种指针组件就是采用了boost里的智能指针方案。很多有用过boost智能指针的朋友,很容易地就能发现它们之间的关间:

std boost 功能说明
unique_ptr scoped_ptr 独占指针对象,并保证指针所指对象生命周期与其一致
shared_ptr shared_ptr 可共享指针对象,可以赋值给shared_ptr或weak_ptr。
指针所指对象在所有的相关联的shared_ptr生命周期结束时结束,是强引用。
weak_ptr weak_ptr 它不能决定所指对象的生命周期,引用所指对象时,需要lock()成shared_ptr才能使用。

C++11将boost里的这一套纳入了标准。


一、boost 智能指针

智能指针是利用RAII(Resource Acquisition Is Initialization:资源获取即初始化)来管理资源。关于RAII的讨论可以参考前面的《40_面向对象编程--虚函数与多态(六)。在使用boost库之前应该先下载后放在某个路径,并在VS 包含目录中添加。下面是boost库里面的智能指针:


C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第1张图片



二、unique_ptr<T>(原来scoped_ptr<T>)

1、示例

#include <iostream>
#include <memory>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    {
        unique_ptr<X> pp(new X);

        //boost::unique_ptr<X> p2(pp); //Error:所有权不能转移
    }
    cout << "Exiting main ..." << endl;

    return 0;
}

运行结果:

C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第2张图片


2、源码分析

来稍微看一下scoped_ptr的简单定义:

namespace boost
{

    template<typename T> class scoped_ptr : noncopyable
    {
    private:

        T *px;

        scoped_ptr(scoped_ptr const &);
        scoped_ptr &operator=(scoped_ptr const &);

        typedef scoped_ptr<T> this_type;

        void operator==( scoped_ptr const & ) const;
        void operator!=( scoped_ptr const & ) const;
    public:
        explicit scoped_ptr(T *p = 0);
        ~scoped_ptr();

        explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
        void reset(T *p = 0);

        T &operator*() const;
        T *operator->() const;
        T *get() const;

        void swap(scoped_ptr &b);
    };

    template<typename T>
    void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}


40_面向对象编程--虚函数与多态(六)》的auto_ptr类似,内部也有一个T* px; 成员 ,智能指针对象pp 生存期到了,调用析构函数,在析构函数内会delete  px; 当调用reset() 函数时也能够释放堆对象,如何实现的呢?

void reset(T *p = 0)  // never throws
{
    BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
    this_type(p).swap(*this);
}
void swap(scoped_ptr &b)  // never throws
{
    T *tmp = b.px;
    b.px = px;
    px = tmp;
}

typedef scoped_ptr<T> this_type;  当调用pp.reset(),reset 函数构造一个临时对象,它的成员px=0, 在swap 函数中调换 pp.px  与 (this_type)(p).px, 即现在pp.px = 0; //解绑临时对象接管了裸指针(即所有权可以交换),reset 函数返回,栈上的临时对象析构,调用析构函数,进而delete px;

另外拷贝构造函数和operator= 都声明为私有,故所有权不能转移,且因为容器的push_back 函数需要调用拷贝构造函数,故也不能将scoped_ptr 放进vector,这点与auto_ptr 相同(不能共享所有权)。此外,还可以使用 auto_ptr 对象 构造一个scoped_ptr 对象:scoped_ptr( std::auto_ptr<T> p ): px( p.release() );


由于scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此boost::scoped_ptr是不能管理数组对象的,如果要管理数组对象需要使用boost::scoped_array类。


boost::scoped_ptr和std::auto_ptr的功能和操作都非常类似,如何在他们之间选取取决于是否需要转移所管理的对象的所有权(如是否需要作为函数的返回值)。如果没有这个需要的话,大可以使用boost::scoped_ptr,让编译器来进行更严格的检查,来发现一些不正确的赋值操作。




三、shared_ptr<T>

1、示例

#include <iostream>
#include <memory>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    shared_ptr<X> p1(new X);
    cout << p1.use_count() << endl;
    shared_ptr<X> p2 = p1;
    //boost::shared_ptr<X> p3;
    //p3 = p1;

    cout << p2.use_count() << endl;
    p1.reset();
    cout << p2.use_count() << endl;
    p2.reset();
    cout << "Exiting main ..." << endl;
    return 0;
}

C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第3张图片

图示上述程序的过程也就是:

C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第4张图片

再深入一点,可以看源码,但shared_ptr 的实现 比 scoped_ptr 要复杂许多,涉及到多个类。


2、shared_ptr<T>与auto_ptr<T>
(1)之前我们讲过auto_ptr<T>不能放置vector中
(2)但share_ptr<T>可以放置vector中
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    vector<auto_ptr<X> > v;
    auto_ptr<X> p(new X);
    //v.push_back(p);  //Error:auto_ptr调用了=运算符,而push_back传入是const参数,因此不能调用push_back

    vector<shared_ptr<X> > v2;
    shared_ptr<X> p2(new X);
    v2.push_back(p2);
    cout << p2.use_count() << endl;

    return 0;
}
运行结果:
C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第5张图片

3、总结一下:

和前面介绍的scoped_ptr相比,shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。

shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用shared_ptr:

    1.  避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
    2.  shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
    3. 不要构造一个临时的shared_ptr作为函数的参数。
详见 http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm

如下列bad 函数内 的代码则可能导致内存泄漏:
void f(shared_ptr<int>, int);
int g();

void ok()
{
    shared_ptr<int> p(new int(2));
    f(p, g());
}

void bad()
{
    f(shared_ptr<int>(new int(2)), g());
}
如bad 函数内,假设先构造了堆对象,接着执行g(), 在g 函数内抛出了异常,那么由于裸指针还没有被智能指针接管,就会出现内存泄漏。



四、weak_ptr<T>
1、shared_ptr<T>的缺点:循环问题
如上总结shared_ptr<T> 时说到引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
#include <iostream>
#include <memory>
using namespace std;

class Parent;
class Child;
typedef shared_ptr<Parent> parent_ptr;
typedef shared_ptr<Child> child_ptr;

class Child
{
public:
    Child()
    {
        cout << "Child ..." << endl;
    }
    ~Child()
    {
        cout << "~Child ..." << endl;
    }
    parent_ptr parent_;
};

class Parent
{
public:
    Parent()
    {
        cout << "Parent ..." << endl;
    }
    ~Parent()
    {
        cout << "~Parent ..." << endl;
    }
    child_ptr child_;
};

int main(void)
{
    parent_ptr parent(new Parent);  //1
    child_ptr child(new Child);     //1
    parent->child_ = child;         //2
    child->parent_ = parent;        //2
    //parent->child_.reset();  //Error:解决析构问题
    return 0;
}
运行结果:

问题:没有调用析构函数
如上述程序的例子,运行程序可以发现Child 和 Parent 构造函数各被调用一次,但析构函数都没有被调用。由于Parent和Child对象互相引用,

它们的引用计数最后都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了内存泄漏

其中一种解决循环引用问题的办法是 手动打破循环引用,如在return 0; 之前加上一句 parent->child_.reset(); 此时

当栈上智能指针对象child 析构,Child 对象引用计数为0,析构Chlid 对象,它的成员parent_ 被析构,则Parent 对象引用计数减为1,故当栈上智能指针对象parent 析构时,Parent 对象引用计数为0,被析构。


2、弱引用智能指针 weak_ptr<T>
C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第6张图片
但手动释放不仅麻烦而且容易出错,这里主要介绍一下弱引用智能指针 weak_ptr<T> 的用法。

强引用与弱引用

(1)强引用,只要有一个引用存在,对象就不能释放

(2)弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在

【1】如果存在,提升为shared_ptr(强引用)成功
【2】如果不存在,提升失败

(3)通过weak_ptr访问对象的成员的时候,要提升为shared_ptr


(4)对于上述的例子,只需要将Parent 类里面的成员定义改为如下,即可解决循环引用问题:

class Parent
{
public:
    weak_ptr<parent> child_;
};



#include <iostream>
#include <memory>
using namespace std;

class Parent;
class Child;
typedef shared_ptr<Parent> parent_ptr;
typedef shared_ptr<Child> child_ptr;

class Child
{
public:
    Child()
    {
        cout << "Child ..." << endl;
    }
    ~Child()
    {
        cout << "~Child ..." << endl;
    }
    parent_ptr parent_;
};

class Parent
{
public:
    Parent()
    {
        cout << "Parent ..." << endl;
    }
    ~Parent()
    {
        cout << "~Parent ..." << endl;
    }
    weak_ptr<Child> child_;
};

int main(void)
{
    parent_ptr parent(new Parent);  
    child_ptr child(new Child);     
    parent->child_ = child;         
    child->parent_ = parent;        
    return 0;
}
运行结果:

C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第7张图片

因为此例子涉及到循环引用,而且是类成员引用着另一个类,涉及到两种智能指针,跟踪起来难度很大,我也没什么心情像分析shared_ptr 一样画多个图来解释流程,这个例子需要解释的代码远远比shared_ptr 多,这里只是解释怎样使用,有兴趣的朋友自己去分析一下。


(5)下面再举个例子说明lock() 和 expired() 成员函数函数的用法:

【1】lock():提升为share_ptr

【2】expired():的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。

#include <iostream>
#include <memory>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }

    void Fun()
    {
        cout << "Fun ..." << endl;
    }
};
int main(void)
{
    weak_ptr<X> p;
    shared_ptr<X> p3;
    {
        shared_ptr<X> p2(new X);
        cout << p2.use_count() << endl;
        p = p2;
        cout << p2.use_count() << endl;

        /*shared_ptr<X> */p3 = p.lock();
        cout << p3.use_count() << endl;
        if (!p3)
            cout << "object is destroyed" << endl;
        else
            p3->Fun();
    }
    /*
    shared_ptr<X> p4 = p.lock();
    if (!p4)
    cout<<"object is destroyed"<<endl;
    else
    p4->Fun();
    */

    if (p.expired())
        cout << "object is destroyed" << endl;
    else
        cout << "object is alived" << endl;

    return 0;
}

运行结果:
C++ Primer 学习笔记_56_STL剖析(十一)(原boost库):详解智能指针(unique_ptr(原scoped_ptr) 、shared_ptr 、weak_ptr源码分析)_第8张图片

从输出可以看出,当p = p2; 时并未增加use_count_,所以p2.use_count() 还是返回1,而从p 提升为 p3,增加了use_count_, p3.use_count() 返回2;出了大括号,p2 被析构,use_count_ 减为1,程序末尾结束,p3 被析构,use_count_ 减为0,X 就被析构了。





参考 :

C++ primer 第四版
Effective C++ 3rd
C++编程规范

你可能感兴趣的:(C++,STL)