一 enable_shared_from_this模板类详解
什么时候该使用enable_shared_from_this模板类?当我们需要一个类对象返回本身并且该类使用了shared_ptr智能指针时,就需要使用enable_shared_from_this。并且需要注意,当我们使用智能指针管理资源时,必须统一使用智能指针,而不能再某些地方使用智能指针,某些地方使用原始指针,否则不能保持智能指针的语义,从而产生各种错误。
1 错误使用this返回对象本身
错误代码如下:
class Test
{
public:
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
shared_ptr<Test> GetObject(){
return shared_ptr<Test>(this);
}
};
void test11(){
{
shared_ptr<Test> p(new Test());
shared_ptr<Test> q = p->GetObject();
cout<<endl;
}
}
结果,原因是析构了两次new出来的Test:
为什么会这样呢?原因出在GetObject函数。它里面使用this给shared_ptr赋值,这就会导致原本shared_ptr p(new Test())时,该内存已经被p管理。当调用至shared_ptr(this),返回的匿名对象会被编译器再次用其创建新的匿名对象返回,然而,该匿名对象是由裸指针this赋值的,这就导致p与q各自引用相同的内存,但是引用计数都是1,因为p与q这两个shared_ptr是没有任何关系的。所以当p与q生命周期结束后,必定使同一片内存被释放两次导致报段错误。
实际上上面的错误就是下面例子。
int *p = new int(12);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);//必定报错,原因是两个没有任何关系的shared_ptr绑定同一片内存,导致释放两次。
2 改正this,使用enable_shared_from_this模板类返回对象本身
上面是错误的绑定this来获取shared_ptr即获取对象本身。那么有什么方法从一个类的成员函数中获取当前对象的shared_ptr呢,其实方法很简单:只需要该类继承至enable_shared_from_this模板类,然后调用该基类enable_shared_from_this中的shared_from_this即可。
正确代码:
class Test: public enable_shared_from_this<Test>//修改1
{
public:
//析构函数
~Test() { std::cout << "Test Destructor." << std::endl; }
//获取指向当前对象的指针
shared_ptr<Test> GetObject(){
//return shared_ptr<Test>(this);
return shared_from_this();//修改2
}
};
void test11(){
{
shared_ptr<Test> p(new Test());
shared_ptr<Test> q = p->GetObject();
cout<<endl;
}
}
那为什么继承enable_shared_from_this后就可以正常使用呢?所以我们需要分析enable_shared_from_this这个类了,继续往下看。
3 分析enable_shared_from_this源码
为了方便理解,我们使用boost库的enable_shared_from_this类模板源码,实际上标准库的也差不多,标准库使用友元处理,而boost库使用public处理。
enable_shared_from_this.hpp文件,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_;
};
首先我们看到它有三个构造函数,而实际的public公有方法即能被调用的函数只有三个,其中_internal_accept_owner还被注明不能调用。所以该类非常简单,着重看不加const的shared_from_this(或者const的shared_from_this函数)外加私有成员weak_this_即可。
然后我们分析:
- 1)如果想调用shared_from_this返回shared_ptr,那么就必须要初始化weak_this_ ,因为shared_ptr是由weak_this_赋值创建的,这里解释一下为什么使用若引用weak_ptr类型作为成员:因为弱引用不占引用计数,若我们使用shared_ptr强引用,那么该成员就必定占用一个计数,就会导致该内存本该由1->0释放,但是由于该成员的存在导致无法释放,造成内存延时释放,与逻辑不匹配,所以必须使用弱引用作为成员。
- 2)接着上面讲,因为shared_ptr是由weak_this_赋值创建的,所以weak_this_必须被初始化,而整个类中只有_internal_accept_owner是初始化weak_this_的,该函数由shared_ptr构造时自动调用。所以目前逻辑就清楚了,只有shared_ptr构造被完整调用后,weak_this_才有值,然后shared_from_this才能被成功返回。但是注意,构造初始化完全的顺序刚好与这个顺序有点相反。例如:
shared_ptr<Test> p(new Test());
首先进入shared_ptr,但是会先去执行Test的构造,然后又因为enable_shared_from_this是Test的基类,所以最终先去执行完enable_shared_from_this的构造,再返回Test的构造执行完,最后返回shared_ptr的构造执行完。但是我们写代码时只需要记住必须只有shared_ptr被先执行,才能进入Test与enable_shared_from_this的构造。而不能越过shared_ptr的构造直接调用Test的构造和enable_shared_from_this的构造,这必然是错误的,因为没有shared_ptr的构造初始化weak_this_,shared_from_this返回的p肯定是非法的。
所以我们可以根据上面所说,列出常见继承于enable_shared_from_this类后,使用错误的代码。
错误1:
class Test : public enable_shared_from_this<Test>
{
Test() { shared_ptr<Test> pTest = shared_from_this(); }
};
这种写法必然是错误的,上面已经说过,不能在构造shared_ptr完成之前调用shared_from_this,而enable_shared_from_this的构造和Test的构造必定在shared_ptr构造函数完成之前,所以shared_ptr的构造函数必定未执行完毕,也就是说weak_this_未被初始化,所以shared_from_this返回的是一个未知状态值。
有人就会说,那在pTest的构造函数之前创建一个shared_ptr不就行了吗,好,那我们就看错误2。
错误2:
Test() {
shared_ptr<Test> sh(new Test());
shared_ptr<Test> pTest = sh->shared_from_this();
//shared_ptr<Test> pTest = shared_from_this();
}
错误2这种情况实际上我真的不想列出来,当我们在Test构造中new一个Test本身,就会造成递归死循环调用该构造,根本就不会再往下执行,并且死循环过程中出现段错误,原因我猜可能是循环不断new,而没有释放内存,编译器认为出问题直接报错,因为我测试过不断new而不释放内存最终导致死机的情况。所以这种情况不再多说。
错误3:
class Test : public enable_shared_from_this<Test>
{
void func() { shared_ptr<Test> pTest = shared_from_this(); }
};
int main()
{
Test test;
test.func(); //错误
// Test *pTest = new Test;
// pTest->func(); //同理错误
}
上面错误3的代码实际上错误1也是一样,都是没有执行完整shared_ptr的构造函数,导致weak_this_未被初始化。
gdb调试结果也报错weak_this_未被初始化。
正确的写法就好像我们第2大点的调用例子,必须先调用shared_ptr执行完整的shared_ptr构造函数初始化weak_this_ 后,才能调用shared_from_this。
正确写法:
注意:func是返回void,我们只是在函数体内测试是否能获取shared_ptr pTest = shared_from_this(),实际上我们获取本身直接将shared_from_this返回即可,当然返回值也需要换换。
class Test : public enable_shared_from_this<Test>
{
void func() { shared_ptr<Test> pTest = shared_from_this(); }
};
int main()
{
shared_ptr<Test> pTest( new Test() );
pTest->func();
}
4 总结enable_shared_from_this的用法
- 1)实际上enable_shared_from_this的用法非常简单,就是像第2大点那样继承,然后使用即可。只不过理解透需要点时间。
- 2)获取本类对象本身,必须先调用shared_ptr p(new Test())执行完整个shared_ptr的构造函数,才能调用shared_from_this获取对象本身,而不能直接调用Test test这些自定义类对象的构造后,就调用shared_from_this,这是错误的。