关于boost中enable_shared_from_this类的原理分析
首先要说明的一个问题是:如何安全地将this指针返回给调用者。一般来说,我们不能直接将this指针返回。想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变量并不知道,此时如果外部变量使用这个指针,就会使得程序崩溃。
使用智能指针shared_ptr看起来是个不错的解决方法。但问题是如何去使用它呢?我们来看如下代码:
#include <iostream>
#include <boost/shared_ptr.hpp>
class Test
{
public:
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
boost::shared_ptr<Test> GetObject()
{
boost::shared_ptr<Test> pTest(this);
return pTest;
}
};
int main(int argc, char *argv[])
{
{
boost::shared_ptr<Test> p( new Test( ));
std::cout << "q.use_count(): " << q.use_count() << std::endl;
boost::shared_ptr<Test> q = p->GetObject();
}
return ;
}
运行后,程序输出:
  Test Destructor.
q.use_count(): 1
  Test Destructor.
可以看到,对象只构造了一次,但却析构了两次。并且在增加一个指向的时候,shared_ptr的计数并没有增加。也就是说,这个时候,p和q都认为自己是Test指针的唯一拥有者,这两个shared_ptr在计数为0的时候,都会调用一次Test对象的析构函数,所以会出问题。
那么为什么会这样呢?给一个shared_ptr<Test>传递一个this指针难道不能引起shared_ptr<Test>的计数吗?
答案是:对的,shared_ptr<Test>根本认不得你传进来的指针变量是不是之前已经传过。
看这样的代码:
int main()
{
Test* test = new Test();
shared_ptr<Test> p(test);
shared_ptr<Test> q(test);
std::cout << "p.use_count(): " << p.use_count() << std::endl;
std::cout << "q.use_count(): " << q.use_count() << std::endl;
return ;
}
运行后,程序输出:
p.use_count(): 1
q.use_count(): 1
Test Destructor.
Test Destructor.
也证明了刚刚的论述:shared_ptr<Test>根本认不得你传进来的指针变量是不是之前已经传过。
事实上,类对象是由外部函数通过某种机制分配的,而且一经分配立即交给 shared_ptr管理,而且以后凡是需要共享使用类对象的地方,必须使用这个 shared_ptr当作右值来构造产生或者拷贝产生(shared_ptr类中定义了赋值运算符函数和拷贝构造函数)另一个shared_ptr ,从而达到共享使用的目的。
解释了上述现象后,现在的问题就变为了:如何在类对象(Test)内部中获得一个指向当前对象的shared_ptr 对象?如果我们能够做到这一点,直接将这个shared_ptr对象返回,就不会造成新建的shared_ptr的问题了。
下面来看看enable_shared_from_this类的威力。
enable_shared_from_this 是一个以其派生类为模板类型参数的基类模板,继承它,派生类的this指针就能变成一个 shared_ptr。
有如下代码:
#include <iostream>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
class Test : public boost::enable_shared_from_this<Test> //改进1
{
public:
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
boost::shared_ptr<Test> GetObject()
{
return shared_from_this(); //改进2
}
};
int main(int argc, char *argv[])
{
{
boost::shared_ptr<Test> p( new Test( ));
std::cout << "q.use_count(): " << q.use_count() << std::endl;
boost::shared_ptr<Test> q = p->GetObject();
}
return ;
}
运行后,程序输出:
Test Destructor.
q.use_count(): 2;
可以看到,问题解决了!
接着来看看enable_shared_from_this 是如何工作的,以下是它的源码:
template<class T> class enable_shared_from_this
{
protected: BOOST_CONSTEXPR enable_shared_from_this() BOOST_SP_NOEXCEPT
{
} BOOST_CONSTEXPR enable_shared_from_this(enable_shared_from_this const &) BOOST_SP_NOEXCEPT
{
} enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_SP_NOEXCEPT
{
return *this;
} ~enable_shared_from_this() BOOST_SP_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;
} weak_ptr<T> weak_from_this() BOOST_SP_NOEXCEPT
{
return weak_this_;
} weak_ptr<T const> weak_from_this() const BOOST_SP_NOEXCEPT
{
return weak_this_;
} 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 BOOST_SP_NOEXCEPT
{
if( weak_this_.expired() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
} private: mutable weak_ptr<T> weak_this_;
}; } // namespace boost #endif // #ifndef BOOST_SMART_PTR_ENABLE_SHARED_FROM_THIS_HPP_INCLUDED
标黄部分是shared_from_this()函数的实现。可以看到,这个函数使用一个weak_ptr对象(weak_this_)来构造一个 shared_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来做拷贝。(PS:在shared_ptr类中,是有使用weak_ptr对象来构造shared_ptr对象的构造函数的:
template<class Y>
    explicit shared_ptr( weak_ptr<Y> const & r ): pn( r.pn )
)
对于第二个问题,假设我在类里储存了一个指向自身的shared_ptr,那么这个 shared_ptr的计数最少都会是1,也就是说,这个对象将永远不能析构,所以这种做法是不可取的。
在enable_shared_from_this类中,没有看到给成员变量weak_this_初始化赋值的地方,那究竟是如何保证weak_this_拥有着Test类对象的指针呢?
首先我们生成类T时,会依次调用enable_shared_from_this类的构造函数(定义为protected),以及类Test的构造函数。在调用enable_shared_from_this的构造函数时,会初始化定义在enable_shared_from_this中的私有成员变量weak_this_(调用其默认构造函数),这时的weak_this_是无效的(或者说不指向任何对象)。
接着,当外部程序把指向类Test对象的指针作为初始化参数来初始化一个shared_ptr(boost::shared_ptr<Test> p( new Test( ));)。
现在来看看 shared_ptr是如何初始化的,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() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
}
而在这里,对enable_shared_from_this 类的成员weak_this_进行拷贝赋值,使得weak_this_作为类对象 shared_ptr 的一个观察者。
这时,当类对象本身需要自身的shared_ptr时,就可以从这个weak_ptr来生成一个了:
shared_ptr<T> shared_from_this()
{
shared_ptr<T> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}
以上。
从上面的说明来看,需要小心的是shared_from_this()仅在shared_ptr<T>的构造函数被调用之后才能使用,原因是enable_shared_from_this::weak_this_并不在构造函数中设置,而是在shared_ptr<T>的构造函数中设置。
所以,如下代码是错误的:
class D:public boost::enable_shared_from_this<D>
{
public:
D()
{
boost::shared_ptr<D> p=shared_from_this();
}
};
原因是在D的构造函数中虽然可以保证enable_shared_from_this<D>的构造函数被调用,但weak_this_是无效的(还还没被接管)。
如下代码也是错误的:
class D:public boost::enable_shared_from_this<D>
{
public:
void func()
{
boost::shared_ptr<D> p=shared_from_this();
}
};
void main()
{
D d;
d.func();
}
原因同上。
总结为:不要试图对一个没有被shared_ptr接管的类对象调用shared_from_this(),不然会产生未定义行为的错误。
参考:
Boost 库 Enable_shared_from_this 实现原理分析
如何用enable_shared_from_this 来得到指向自身的shared_ptr及对enable_shared_from_this 的理解
enable_shared_from_this模板类使用完全解析
关于boost中enable_shared_from_this类的原理分析的更多相关文章
- 【转载】Lua中实现类的原理
		
原文地址 http://wuzhiwei.net/lua_make_class/ 不错,将metatable讲的很透彻,我终于懂了. --------------------------------- ...
 - Java中Atomic类的使用分析
		
1:为什么会出现Atomic类 在多线程或者并发环境中,我们常常会遇到这种情况 int i=0; i++ 稍有经验的同学都知道这种写法是线程不安全的.为了达到线程安全的目的,我们通常会用synchro ...
 - boost库----enable_shared_from_this类的作用和实现原理
		
使用boost库时,经常会看到如下的类 class A:public enable_share_from_this<A> 在什么情况下要使类A继承enable_share_from_thi ...
 - String类中intern方法的原理分析
		
一,前言  昨天简单整理了JVM内存分配和String类常用方法,遇到了String中的intern()方法.本来想一并总结起来,但是intern方法还涉及到JDK版本的问题,内容也相对较多,所以今 ...
 - RxJava 中的Map函数原理分析
		
首先看一段Map函数的使用代码: Observable.create(new Observable.OnSubscribe<Integer>() { @Override public vo ...
 - Android中线程间通信原理分析:Looper,MessageQueue,Handler
		
自问自答的两个问题 在我们去讨论Handler,Looper,MessageQueue的关系之前,我们需要先问两个问题: 1.这一套东西搞出来是为了解决什么问题呢? 2.如果让我们来解决这个问题该怎么 ...
 - 并发包中automic类的原理
		
提到同步,我们一般首先想到的是lock,synchronized,但java中有一套更加轻量级的同步方式即atomic类.java的并发原子包里面提供了很多可以进行原子操作的类,比如: AtomicI ...
 - TCP中ECN的工作原理分析二(摘自:RFC3168)
		
英文源:http://www.icir.org/floyd/ecn.html 发送端和接收端处理: The TCP Sender For a TCP connection using ECN, new ...
 - Android中Input型输入设备驱动原理分析(一)
		
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
 
随机推荐
- samba服务器之无认证进入共享目录
			
修改设备中的/log/samba/lib/smb.conf文件 security = share [web] ...
 - C#设计模式(6)——原型模式
			
1.原型模式介绍 在软件系统开发中,有时候会遇到这样的情况:我们需要用到多个相同实例,最简单直接的方法是通过多次调用new方法来创建相同的实例.如下: Person person=}; Person ...
 - 当php邂逅windows通用上传缺陷
			
早上逛乌云发现了PKAV大牛的一篇文章,针对php和windows文件上传的分析,思路很YD,果断转之与大家分享. 虽然此文可能有许多的限制条件,但是如果你认真阅读会发现,其实还是比较实用的. 另外一 ...
 - 【leetcode-125】 验证回文串
			
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 示例 1: 输入: "A man, a plan, a c ...
 - 【不懂】spring bean生命周期
			
完整的生命周期(牢记): 1.spring容器准备 2.实例化bean 3.注入依赖关系 4.初始化bean 5.使用bean 6.销毁bean Bean的完整生命週期可以認為是從容器建立初始化Bea ...
 - hdu 6383
			
题意是说给定一个序列,能否通过任意次对部分数字 +1,对部分数字 -2的操作使得序列在满足全部非负且任意两元素的差值不超过1的前提下最小值最大,求最大值. 一开始的时候没有注意到整个序列全是非负数,还 ...
 - Spark源码剖析 - 计算引擎
			
本章导读 RDD作为Spark对各种数据计算模型的统一抽象,被用于迭代计算过程以及任务输出结果的缓存读写.在所有MapReduce框架中,shuffle是连接map任务和reduce任务的桥梁.map ...
 - CSS魔法(五)项目实战
			
三大标签--title.description.keyword 淘宝网 <title>淘宝网 - 淘!我喜欢</title> <meta name="spm ...
 - Eclipse 项目导航字体设置 左侧树字体
			
在解压的文件下 E:\eclipse\plugins\org.eclipse.ui.themes_1.2.0.v20170517-0622\css\dark 找到 e4-dark_basestyle ...
 - daemon_init函数:调用该函数把普通进程转变为守护进程
			
#include <unistd.h> #include <syslog.h> #include <fcntl.h> #include <signal.h&g ...