• 详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)


    一、boost 智能指针

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



    (一)、scoped_ptr<T>

    先来看例程:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     
    #include <boost/scoped_ptr.hpp>
    #include <iostream>
    using namespace std;

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

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

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

        return 0;
    }



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

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    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);
    }

    auto_ptr类似,内部也有一个T* px; 成员 ,智能指针对象pp 生存期到了,调用析构函数,在析构函数内会delete  px; 如下面所说:

     scoped_ptr mimics a built-in pointer except that it guarantees deletion of the object pointed to, either on destruction of the scoped_ptr or via an

     explicit reset(). scoped_ptr is a simple solution for simple needs; use shared_ptr or std::auto_ptr if your needs are more complex.


    从上面的话可以得知当调用reset() 函数时也能够释放堆对象,如何实现的呢?

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    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>

    An enhanced relative of scoped_ptr with reference counted copy semantics. The object pointed to is deleted when the last 

    shared_ptr pointing to it  is destroyed or reset.

    先来看例程:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    #include <boost/shared_ptr.hpp>
    #include <iostream>
    using namespace std;

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

    int main(void)
    {
        cout << "Entering main ..." << endl;
        boost::shared_ptr<X> p1(new X);
        cout << p1.use_count() << endl;
        boost::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;
    }

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


    再深入一点,看源码,shared_ptr 的实现 比 scoped_ptr 要复杂许多,涉及到多个类,下面就不贴完整源码,看下面的类图:


    执行 new X); 这一行之后:

    而执行 p1.use_count(); 先是 pn.use_count();  接着 pi_ != 0? pi_->use_count(): 0;  return use_count_; 即返回1.


    接着执行  boost::shared_ptr<X> p2 = p1; 

    在shared_count 类的拷贝构造函数设置断点,然后就可以跟踪进去,如下的代码:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
     
    // shared_count
    shared_count(shared_count const &r): pi_(r.pi_)  // nothrow
    {
        if( pi_ != 0 ) pi_->add_ref_copy();
    }
    // sp_counted_base
    void add_ref_copy()
    {
        BOOST_INTERLOCKED_INCREMENT( &use_count_ );
    }

    sp_counted_impl_p 对象,所以此时无论打印p2.use_count(); 还是 p1.use_count(); 都是2。

    接着执行p1.reset(); 


     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    // shared_ptr
    void reset() // never throws in 1.30+
    {
        this_type().swap(*this);
    }
    void swap(shared_ptr<T> &other)  // never throws
    {
        std::swap(px, other.px);
        pn.swap(other.pn);
    }

    this_type() 构造一个临时对象,px = 0, pn.pi_ =  0; 然后swap交换p1 与 临时对象的成员,即现在p1.px = 0; p1.pn.p1_ = 0; 如上图。

    reset 函数返回,临时对象需要析构,但跟踪时却发现直接返回了,原因跟上面的一样,因为shared_ptr 没有实现析构函数,调用的是默认的析构函

    数,与上面拷贝函数同样的道理,可以在shared_count 类析构函数设置断点,因为pn 是对象成员,故析构函数也会被调用。如下代码:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    //shared_count
    ~shared_count() // nothrow
    {
        if( pi_ != 0 ) pi_->release();
    }

    // sp_counted_base
    void release() // nothrow
    {
        if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
        {
            dispose();
            weak_release();
        }
    }

    现在use_count_ 减为1,但还不为0,故 weak_release(); 两个函数没有被调用。当然此时打印 p2.use_count() 就为1 了。

    最后 p2.reset(); 跟p1.reset(); 同样的流程,只不过现在执行到release 时,use_count_ 减1 为0;需要继续执行dispose(); 和

    weak_release();  如下代码:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     

    //sp_counted_impl_p
    virtual void dispose() // nothrow
    {
        boost::checked_delete( px_ );
    }
    //sp_counted_base
    void weak_release() // nothrow
    {
        if( BOOST_INTERLOCKED_DECREMENT( &weak_count_ ) == 0 )
        {
            destroy();
        }
    }

    virtual void destroy() // nothrow
    {
        delete this;
    }

    在check_delete 中会 delete px_; 也就是析构 X。接着因为weak_count_ 减1 为0, 故执行destroy();  函数里面delete this; 即析构自身

    (sp_counted_impl_p 对象是在堆上分配的)。

    说到这里,我们也可以明白,即使最后没有调用p2.reset(); 当p2 栈上对象生存期到, 需要调用shared_ptr 类析构函数,进而调用shared_count 类析

    构函数,所以执行的结果也是跟reset() 一样的,只不过少了临时对象this_type()的构造。


    总结一下:

    和前面介绍的boost::scoped_ptr相比,boost::shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的

    使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。

    boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::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 函数内 的代码则可能导致内存泄漏:
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    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>

    如上总结shared_ptr<T> 时说引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     
    #include <boost/shared_ptr.hpp>
    #include <iostream>
    using namespace std;

    class Parent;
    class Child;
    typedef boost::shared_ptr<Parent> parent_ptr;
    typedef boost::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);
        child_ptr child(new Child);
        parent->child_ = child;
        child->parent_ = parent;

        return 0;
    }

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


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

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

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

    但手动释放不仅麻烦而且容易出错,这里主要介绍一下弱引用智能指针 weak_ptr<T> 的用法,下面是简单的定义:
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    namespace boost
    {

        template<typename T> class weak_ptr
        {
        public:
            template <typename Y>
            weak_ptr(const shared_ptr<Y> &r);

            weak_ptr(const weak_ptr &r);

            template<class Y>
            weak_ptr &operator=( weak_ptr<Y> && r );

            template<class Y>
            weak_ptr &operator=(shared_ptr<Y> const &r);


            ~weak_ptr();

            bool expired() const;
            shared_ptr<T> lock() const;
        };
    }
    上面出现了 && 的用法,在这里并不是逻辑与的意思,而是C++ 11中的新语法,如下解释:

    && is new in C++11, and it signifies that the function accepts an RValue-Reference -- that is, a reference to an argument that is about 

    to be destroyed. 

    两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用智能指针。

    强引用与弱引用

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

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

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

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

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

     C++ Code 
    1
    2
    3
    4
    5
     
    class Parent
    {
    public:
        boost::weak_ptr<parent> child_;
    };

    因为此例子涉及到循环引用,而且是类成员引用着另一个类,涉及到两种智能指针,跟踪起来难度很大,我也没什么心情像分析

    shared_ptr 一样画多个图来解释流程,这个例子需要解释的代码远远比shared_ptr 多,这里只是解释怎样使用,有兴趣的朋友自

    己去分析一下。

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

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
     
    #include <boost/shared_ptr.hpp>
    #include <boost/weak_ptr.hpp>
    #include <boost/scoped_array.hpp>
    #include <boost/scoped_ptr.hpp>
    #include <iostream>
    using namespace std;

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

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

            /*boost::shared_ptr<X> */
            p3 = p.lock();
            cout << p3.use_count() << endl;
            if (!p3)
                cout << "object is destroyed" << endl;
            else
                p3->Fun();
        }
        /*boost::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;
    }


    从输出可以看出,当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++编程规范

    http://www.cnblogs.com/TianFang/

  • 相关阅读:
    Delphi2010 DataSnap 学习(一)
    DLL封装ReportMachine/FastReport报表
    Delphi中的字符串压缩与解压缩
    Delphi 2010 DataSnap封装COM对象
    PHP&MySQL的一个乱码
    Indy接收邮件中文无编码乱码问题
    Asp.net Mvc自定义客户端验证(CheckBox列表的验证)
    浅谈在asp.net mvc3中使用IValidatableObject接口实现Model数据验证
    使用ModelBinder绑定IPrincipal (User)简化ASP.NET MVC单元测试
    asp.net mvc使用TagBuilder的应用程序集
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8471483.html
Copyright © 2020-2023  润新知