如何用enable_shared_from_this 来得到指向自身的shared_ptr 及对enable_shared_from_this 的理解

在看《Linux多线程服务端编程:使用muduo C++网络库》 的时候,在说到如何防止在将对象的 this 指针作为返回值返回给了调用者时可能会造成的 core dump。需使用 enable_share_from_this。

  首先要说明的一个问题是如何安全地将 this 指针返回给调用者。一般来说,我们不能直接将 this 指针返回。想象这样的情况,该函数将 this 指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变量并不知道,那么此时如果外部变量使用这个指针,就会使得程序崩溃。

  那么使用智能指针 shared_ptr 呢?当然,使得 c++ 指针安全目前来说最有效也使用最多的办法就是使用智能指针 shared_ptr。但问题是这里要怎么去使用它。首先假设你已经理解shared_ptr 的工作原理。然后我们来看下面一份代码:

#include <iostream>
#include <memory>

class Bad {
public:
    Bad() { std::cout << "Bad()" << std::endl; }
    ~Bad() { std::cout << "~Bad()" << std::endl; }
    std::shared_ptr<Bad> getPtr() {
        return std::shared_ptr<Bad>(this);
    }
};

int main(int argc, char const *argv[])
{
    std::shared_ptr<Bad> bp1(new Bad);
    std::shared_ptr<Bad> bp2 = bp1->getPtr();
    std::cout << "bp2.use_count: " << bp2.use_count() << std::endl;
    return 0;
}

程序执行结果为:

Bad()
bp2.use_count: 1
~Bad()
~Bad()
a(822,0x7fff737b5300) malloc: *** error for object 0x7fe74bc04b50: pointer being freed was not allocated

我们可以看到,对象只构造了一次,但却析构了两次。并且在增加一个指向的时候 shared_ptr的计数并没有增加。也就是说,这个时候 bp1 和 bp2 都认为只有自己才是这个智能指针的拥有者。其实也就是说,这里建了两个智能指针同时处理 this 指针。每个智能指针在计数为0的时候都会调用一次Bad对象的析构函数。所以会出问题。

  其实现在问题就变成了,如何在对象中获得一个指向当前对象的 shared_ptr 对象。如果我们能够做到这个,那么直接将这个shared_ptr 对象返回,就不会造成新建 shared_ptr 的问题了。

  下面来看看 enable_shared_from_this;

  enable_shared_from_this 是一个以其派生类为模板类型实参的基类模板,继承它,派生类的this指针就能变成一个 shared_ptr。先来看下面一份代码:

#include <iostream>
#include <memory>

class Good: public std::enable_shared_from_this<Good>{
public:
    Good() { std::cout << "Good()" << std::endl; }
    ~Good() { std::cout << "~Good()" << std::endl; }

    std::shared_ptr<Good> getPtr() {
        return shared_from_this(); 
    }
};

int main(int argc, char const *argv[])
{
    std::shared_ptr<Good> bp1(new Good);
    std::shared_ptr<Good> bp2 = bp1->getPtr();
    std::cout << "bp2.use_count: " << bp2.use_count() << std::endl;
    return 0;
}

执行结果:

Good()
bp2.use_count: 2
~Good()

解决问题了。

那么下面就来看看 enable_shared_from_this 到底是怎么工作的:这是它的实现源码:

template<class T> class enable_shared_from_this
{
protected:

    enable_shared_from_this() BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
        return *this;
    }

    ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
    {
    }

public:

    shared_ptr<T> shared_from_this()
    {
        shared_ptr<T> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

    shared_ptr<T const> shared_from_this() const
    {
        shared_ptr<T const> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

public: // actually private, but avoids compiler template friendship issues

    // Note: invoked automatically by shared_ptr; do not call
    template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() )
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }

private:

    mutable weak_ptr<T> weak_this_;
};

  我们重点看  shared_from_this 的实现。因为我们就是用它来返回一个指向 this 的智能指针对象的。我们可以看到,这个函数使用一个 weak_ptr 对象来构造一个 shared_ptr 对象,然后将其返回。注意这个 weak_ptr 是实例对象的一个成员,所以对于一个对象来说它一直是同一个,每次在调用 shared_from_this 的时候,就会根据 weak_ptr 来构造一个临时shared_ptr对象

  也许看到这里会产生疑问,这里的 shared_ptr 也是一个临时对象,和前面有什么区别?还有,为什么enable_shared_from_this 不直接保存一个 shared_ptr 成员?

  首先对于第一个问题,这里的每一个shared_ptr都是根据 weak_ptr 来构造的,而每次构造shared_ptr的时候使用的参数是一样的。所以这里根据相同的weak_ptr来构造多个临时 shared_ptr 等价于用一个shared_ptr进行拷贝。

  我们首先要理解,类对象肯定是外部函数通过某种机制分配的,并且如果要使用 shared_ptr 来进行管理的话,那么必须在分配完成后立即交给 shared_ptr。然后在其他地方需要用到这个对象的话也应该使用这个 shared_ptr 作为参数进行拷贝构造或赋值。而我们使用一个 weak_ptr 来进行构造 shared_ptr,作用是一样的(如果理解weak_ptr的工作原理,这个就不难理解了, weak_ptr本身就是为了协同 shared_ptr 的工作而生的)。

  那么enable_shared_from_this为什么不直接保存一个 shared_ptr 成员呢?假设我再类里面储存了一个指向自身的 shared_ptr,那么这个shared_ptr的计数最少都会是1,也就是说,这个对象永远不能析构,所以这样做是错误的。而 weak_ptr 不会引起 shared_ptr 计数的增加。

  enable_shared_from_this 的成员变量在enable_shared_from_this构造的时候是没有指向任何对象的,在第一次调用 shared_ptr 的时候,由 shared_ptr 调用 boost::detail::sp_enable_shared_from_this 然后调用enable_shared_from_this 的 _internal_accept_owner来对其进行初始化。看下面的过程就明白了。

  shared_ptr的构造:

template<class Y>
    explicit shared_ptr( Y * p ): px( p ), pn( p ) 
    {
        boost::detail::sp_enable_shared_from_this( this, p, p );
    }

  这里使用了 boost::detail::sp_enable_shared_from_this

template< class X, class Y, class T >
 inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx,
 Y const * py, boost::enable_shared_from_this< T > const * pe )
{
    if( pe != 0 )
    {
        pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
    }
}

  而在这里面又调用 enable_shared_from_this 的 _internal_accept_owner

template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() ) // 成员函数expired()的功能等价于use_count()==0
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }

  从这个构造过程可以看出,当 weak_ptr 第一次指向一个对象后,它就担起了观察者的角色,一直观察着同一个对象。

 

  从上面的介绍也可以看出,我们不能对一个普通对象去获取shared_ptr,比如:

Good g;
std::shared_ptr<Good> gp = g.shared_from_this();

  这样会产生错误,因为 g 并不在堆而在栈区,然后shared_ptr试图去删除一个栈区上构造的对象,就会产生未定义行为。

   另外一个要注意的就是,shared_from_this 不能在构造函数调用,因为在构造一个对象的时候,它还没被交给 shared_ptr 接管。

posted @ 2014-11-15 18:09  厕所门口~~  阅读(1635)  评论(0编辑  收藏  举报