在看《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. 洛谷 P4345 [SHOI2015]超能粒子炮·改 解题报告

    P4345 [SHOI2015]超能粒子炮·改 题意 求\(\sum_{i=0}^k\binom{n}{i}\),\(T\)组数据 范围 \(T\le 10^5,n,j\le 10^{18}\) 设\ ...

  2. OneProxy构建

    OneProxy的主要功能有:1. 垂直分库 2. 水平分表 3. Proxy集群 4. 读高可用 5. 读写分离(master不参与读) 6. 读写分离(master参与读) 7. 写高可用 8. ...

  3. 【HDU3085】nightmare2 双向BFS

    对于搜索树分支很多且有明确起点和终点的情况时,可以采用双向搜索来减小搜索树的大小. 对于双向BFS来说,与单向最大的不同是双向BFS需要按层扩展,表示可能到达的区域.而单向BFS则是按照单个节点进行扩 ...

  4. 【POJ3635】Full Tank 优先队列BFS

    普通BFS:每个状态只访问一次,第一次入队时即为该状态对应的最优解. 优先队列BFS:每个状态可能被更新多次,入队多次,但是只会扩展一次,每次出队时即为改状态对应的最优解. 且对于优先队列BFS来说, ...

  5. 12: MyBatis之传入参数parameterType

    源链接地址:http://blog.csdn.net/liaoxiaohua1981/article/details/6862764

  6. Codeforces Round #514 (Div. 2) D. Nature Reserve

    http://codeforces.com/contest/1059/problem/D 最大值: 最左下方和最右下方分别有一个点 r^2 - (r-1)^2 = (10^7)^2 maxr<0 ...

  7. SQL Server sp_executesql介绍和使用

    execute相信大家都用的用熟了,简写为exec,除了用来执行存储过程,一般都用来执行动态Sql sp_executesql,sql2005中引入的新的系统存储过程,也是用来处理动态sql的, 如: ...

  8. promise第一篇-简介

    1. 创建一个promise对象 var promise = new Promise(function(resolve, reject){ //异步处理 //处理结束后调用resolve或reject ...

  9. JAVA实现SFTP实例

    最近写的一个JAVA实现SFTP的实例: /* * Created on 2009-9-14 * Copyright 2009 by www.xfok.net. All Rights Reserved ...

  10. 约束Constraints--主键约束、外键约束、唯一约束、检查约束、默认约束、NOT NULL约束、列约束与表约束、创建约束、删除约束

    约束   Including Constraints 以下内容转自:https://www.cnblogs.com/wcl2017/p/7043939.html和http://blog.csdn.ne ...