• 如何用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 接管。

  • 相关阅读:
    1. Dubbo原理解析-Dubbo内核实现之SPI简单介绍 (转)
    经典算法问题的java实现 (二)
    经典算法问题的java实现 (一)
    Bitmap的秘密
    Java Networking: UDP DatagramSocket (翻译)
    Java字节码浅析(二)
    Sql server 浅谈用户定义表类型
    Jquery 动态生成表单 并将表单数据 批量通过Ajax插入到数据库
    ASP.NET获取上传图片的大小
    ASP.Net大文件上传组件详解
  • 原文地址:https://www.cnblogs.com/xiezhw3/p/4099948.html
Copyright © 2020-2023  润新知