在看《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 ;
}

程序执行结果为:

Bad()
bp2.use_count:
~Bad()
~Bad()
a(,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 ;
}

执行结果:

Good()
bp2.use_count:
~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 != )
{
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 接管。

如何用enable_shared_from_this 来得到指向自身的shared_ptr 及对enable_shared_from_this 的理解的更多相关文章

  1. 关于boost中enable_shared_from_this类的原理分析

    首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...

  2. 2016-11-02: boost::enable_shared_from_this

    使用场景 当类对象被shared_ptr管理时,需要在类自己定义的函数中把当前对象作为参数传递给其他函数时,必须传递一个shared_ptr,否则就不能保持shared_ptr管理这个类对象的语义.因 ...

  3. boost enable_shared_from_this

    关于shared_ptr和weak_ptr看以前的:http://www.cnblogs.com/youxin/p/4275289.html The header <boost/enable_s ...

  4. enable_shared_from_this用法分析

    一.背景 在为什么需要异步编程文章末尾提到,"为了使socket和缓冲区(read或write)在整个异步操作的生命周期一直保持活动,我们需要采取特殊的保护措施.你的连接类需要继承自enab ...

  5. C++智能指针的enable_shared_from_this和shared_from_this机制

    前言 之前学习muduo网络库的时候,看到作者陈硕用到了enable_shared_from_this和shared_from_this,一直对此概念是一个模糊的认识,隐约记着这个机制是在计数器智能指 ...

  6. 使用enable_shared_from_this示例

    /*测试enable_shared_from_this*/ #include <iostream> #include <boost/smart_ptr/shared_ptr.hpp& ...

  7. JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解

      前  言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...

  8. JavaScript面向对象基础与this指向问题

      前  言           我们的程序语言经历了从"面向机器".到"面向过程".再到"面向对象"的一个过程.而JavaScript是一 ...

  9. 改变javascript函数内部this指针指向的三种方法

    在查了大量的资料后,我总结了下面的三条规则,这三条规则,已经可以解决目前我所遇到的所有问题.规则0:函数本身是一个特殊类型,大多数时候,可以认为是一个变量. function a() { alert( ...

随机推荐

  1. luogu1712 区间 (尺取法+线段树)

    先把区间按照长度从小到大排序,然后用尺取法来做 大概就是先一点一点把区间算上 直到某个点被覆盖了m次,然后一点一点把最前面的区间扔掉,直到没有点被覆盖m次,这样反复做(相当于是它选择的区间左右端点在那 ...

  2. Java NIO -- 通道 Channel

    通道(Channel):由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的“流”.只不过 Channel本身不能直接访问数据 ...

  3. HDU 3081 Marriage Match II (二分图,并查集)

    HDU 3081 Marriage Match II (二分图,并查集) Description Presumably, you all have known the question of stab ...

  4. [loj2585][APIO2018]新家

    题目 一条街上有\(n\) 个点,坐标为\(x_i\) , 店的种类为\(t_i\) , 开业时间为 \([a_i,b_i]\) ; 定义一种类型到一个点的距离为这种类的点到这个点的最近距离 ; 定义 ...

  5. MSSQL 转 ACCESS 在表格结构上应注意的

    今天在把一个MSSQL数据库转为ACCESS发现了一些问题: 在MSSQL表格中的一个(标识)递增字段转到ACCESS后,变成了 “数字”类型,而不是“自动编号”. 而当在Access中,一个字段类型 ...

  6. JAVA:当数据库重启后连接池没有自动识别的解决办法

    今天发现服务器上的一个服务程序出现问题,软件抛出:Connection reset by peer: socket write error 无法正常提供服务,找了一下原因,原来是因为数据库服务器重启, ...

  7. 关于使用vw单位适配H5项目(二)

    一些比较小的H5页面,我觉得全没有必要一定要使用框架的,比如vue和react之类的,我觉得原生的js,html5也可以写好移动端. 最近刚好要赶10多个h5页面,适配移动端的,各种手机型号都要适配, ...

  8. redis 一主二从三哨兵

    总体部署 一主二从三哨兵 ip地址分配分别为 主 127.0.0.1:6379 从 127.0.0.1:6389 从 127.0.0.1:6399 哨兵 127.0.0.1:26379 哨兵 127. ...

  9. Java_myBatis_xml代理写法

    这种开发方式只需要写好Mapper.xml和对应的Interface就可以了. 1.编写Mapper.xml <?xml version="1.0" encoding=&qu ...

  10. python中的位运算符

    按位运算符是把数字看作二进制来进行计算的.Python中的按位运算法则如下,下表中变量 a 为 60,b 为 13,二进制格式如下: a = 0011 1100 b = 0000 1101 ----- ...