如何用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 ;
}
程序执行结果为:
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 的理解的更多相关文章
- 关于boost中enable_shared_from_this类的原理分析
首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...
- 2016-11-02: boost::enable_shared_from_this
使用场景 当类对象被shared_ptr管理时,需要在类自己定义的函数中把当前对象作为参数传递给其他函数时,必须传递一个shared_ptr,否则就不能保持shared_ptr管理这个类对象的语义.因 ...
- boost enable_shared_from_this
关于shared_ptr和weak_ptr看以前的:http://www.cnblogs.com/youxin/p/4275289.html The header <boost/enable_s ...
- enable_shared_from_this用法分析
一.背景 在为什么需要异步编程文章末尾提到,"为了使socket和缓冲区(read或write)在整个异步操作的生命周期一直保持活动,我们需要采取特殊的保护措施.你的连接类需要继承自enab ...
- C++智能指针的enable_shared_from_this和shared_from_this机制
前言 之前学习muduo网络库的时候,看到作者陈硕用到了enable_shared_from_this和shared_from_this,一直对此概念是一个模糊的认识,隐约记着这个机制是在计数器智能指 ...
- 使用enable_shared_from_this示例
/*测试enable_shared_from_this*/ #include <iostream> #include <boost/smart_ptr/shared_ptr.hpp& ...
- JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解
前 言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...
- JavaScript面向对象基础与this指向问题
前 言 我们的程序语言经历了从"面向机器".到"面向过程".再到"面向对象"的一个过程.而JavaScript是一 ...
- 改变javascript函数内部this指针指向的三种方法
在查了大量的资料后,我总结了下面的三条规则,这三条规则,已经可以解决目前我所遇到的所有问题.规则0:函数本身是一个特殊类型,大多数时候,可以认为是一个变量. function a() { alert( ...
随机推荐
- 洛谷 P3297 [SDOI2013]逃考 解题报告
P3297 [SDOI2013]逃考 题意 给一个平面矩形,里面有一些有标号点,有一个是人物点,人物点会被最近的其他点控制,人物点要走出矩形,求人物点最少被几个点控制过. 保证一开始只被一个点控制,没 ...
- 灯 & 树
这回是两道题一起... [USACO09NOV]灯 [中山市选2009]树 题意:给您一些灯,以及一些边.每次改变一盏灯的时候,它相邻的灯也会变.求把灯状态全部转换的最小操作次数. 解: 解异或方程组 ...
- 洛谷P1044 栈
之前看这题还是一头雾水,现在看:啊啊啊lydnb! 思考了一段时间,发现可以用DP. 令f[i]表示有i辆车时的方案数. 我一开始考虑的是在后面加车,可是这样搞不出状态转移方程来. 然后我考虑从前面加 ...
- Red Hat 6.3安装gcc gc++
首先安装gcc需要相应的rpm依赖包,在安装系统的镜像文件中就有这些rpm包 首先在光驱中选择系统的安装包载入 如果桌面显示有如下的光驱 说明是已经载入了镜像,这时候,需要挂载一下镜像到mnt目录 先 ...
- 关于打包测试环境,百度地图报 Bmap not undefined
https的网站使用百度地图,如果你引用的地址没写对的话,加载不出来百度地图,被认为是不安全的JS内容. https://api.map.baidu.com/api?+你的秘钥+&s=1 应该 ...
- Markdown语法整理
标题 语法格式:'#'+'空格'+'文本',一共6级 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 斜体 语法格式:1个星号包裹,我 ...
- poj 3207(2-SAT+SCC)
传送门:Problem 3207 https://www.cnblogs.com/violet-acmer/p/9769406.html 难点: 题意理解. 题意: 平面上有一个圆,圆上有n个点(分别 ...
- 装饰页面decorators.xml
WEB-INF/decorators.xml 这个配置可以增加页面的 装饰页面
- (stringstream toupper 空格) 词组缩写 hdu2564
词组缩写 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- 2636652995 揭秘骗子qq
3042952272636652995755610392020068008这是个骗子群526875508,群里都是群主的小号,付钱之后不给东西,还在群里维护骗子的利益,很明显了.都是骗子小号了,付完整 ...