继承树中多个 enable_shared_from_this 的解决方案






4.73/5 (5投票s)
STL 或 Boost C++ 的 enable_shared_from_this 的初始实现会在用户类继承树中出现多次时导致崩溃。本文提供了一个解决此问题的方法。(搜索标签:enable shared from this)
引言
Boost C++ 库中的智能指针,以及 C++11 中的智能指针,提供了一种在成员函数中从 this
指针检索 shared_ptr
的机制。这通过 enable_shared_from_this
模板类来实现。需要从 this
获取 shared_ptr
的一个很好的例子是,当您想在成员函数中调用一个函数 func
,并且需要将 this
作为参数传递给函数 func
时。通过将 this
作为原始指针传递,您会违反不混合指向同一内存块的 shared_ptr
和原始指针的规则,从而削弱了 shared_ptr
的有用性。使用 enable_shared_from_this
,您可以将 this
的 shared_ptr
传递给函数 func
,确保您的程序中仅使用指向对象分配空间的 shared_ptr
。
enable_shared_from_this
模板类有一个指向 this
的 weak_ptr
成员,以及一个 shared_from_this
方法,该方法通过锁定该 weak_ptr
来返回指向 this
的 shared_ptr
。weak_ptr
的初始化方式是在 shared_ptr
构造函数中,通过一个巧妙的技巧来静态确定正在包装的对象是否继承自 enable_shared_from_this
。
问题在于,它假设只有一个 weak_ptr
需要初始化。如果 enable_shared_from_this
在继承树中出现不止一次,会怎样呢?如下所示:
在这种情况下,要么 enable_shared_from_this
的 weak_ptr
中的一个会被初始化,要么都不被初始化,具体取决于编译器(是的,是真的,不同的编译器行为不同!)。所以当一个 Derived
类型的对象尝试通过 Base
(例如,在 Base
的某个方法中)和/或 Derived
的某个方法访问其 shared_from_this
时,即使该类已经被实例化为 shared_ptr
,它也不会被初始化为 this
。通常,人们会认为只要 Derived
是通过 shared_ptr
实例化的,shared_from_this
就总是被初始化的,但在那种情况下,事实并非如此,任何对其的访问都会导致段错误。
本文提出了一种初始化继承树中所有 enable_shared_from_this
的 weak_ptr
的解决方案。
背景
如果您在尝试间接调用 shared_from_this()
后遇到崩溃,并且您确定您的类对象都由 shared_ptr
指向,那么请检查类的继承树。如果在树中有超过一个 enable_shared_from_this
,那么问题就在于此。
这发生在我身上,因为我实现了一个 SharedObserver
模式(使用 shared_ptr
的观察者模式)。在该模式中,我在 Subject
的 notify
方法中通过 shared_from_this()
将 Subject
发送给 Observer
。因此,SharedSubject
需要继承自 enable_shared_from_this
。我的程序中的一个具体 Subject
也需要使用 shared_from_this()
,因此第二次继承自 enable_shared_from_this
,这就导致了崩溃以及 dẫn đến tôi đến giải pháp này 的调查。
Using the Code
首先,我需要稍微修改 enable_shared_from_this
,所以我创建了一个名为 EnableSharedFromThis
的类。因此,让您的类继承自 EnableSharedFromThis
而不是 enable_shared_from_this
。
class Base : public EnableSharedFromThis< Base >
{
public:
Base(int iValue) : mValue(iValue) {}
void BaseFunc()
{
FuncBase(EnableSharedFromThis< Base >::SharedFromThis());
}
void PrintValue()
{
std::cout << "Value = " << mValue << std::endl;
}
private:
int mValue;
};
class Derived : public Base, public EnableSharedFromThis< Derived >
{
public:
Derived(int iValue) : Base(iValue) {}
void DerivedFunc()
{
FuncDerived(EnableSharedFromThis< Derived >::SharedFromThis());
}
};
这是 FuncBase
和 FuncDerived
的实现,它们是两个自由函数,分别接收 Base
和 Derived
的 shared_ptr
。它们分别在 Base
和 Derived
的成员函数内部被调用。这两个成员函数都将相应的 SharedFromThis
作为参数传递给自由函数。
void FuncBase(std::shared_ptr< Base > iBase)
{
iBase->PrintValue();
}
void FuncDerived(std::shared_ptr< Derived > iDerived)
{
iDerived->PrintValue();
}
然后我创建了一个 SmartPtrBuilder
,其中包含一个 static
模板方法 CreateSharedPtr
。您可以调用它,并指定一个可变数量的模板参数,每个参数对应一个需要初始化的继承自 EnableSharedFromThis
的类。
// You must specify all the classes that inherits from EnableSharedFromThis
// in Derived inheritance tree as template argument
std::shared_ptr< Derived > wDerived = SmartPtrBuilder::CreateSharedPtr
< Derived, Base >(new Derived(123));
然后您就可以正常使用 wDerived
,并且它所有的 internal weak_ptrs
都将被初始化。
// One or both of the following would crash if using the original
// std::enable_shared_from_this
wDerived->BaseFunc();
wDerived->DerivedFunc();
关注点
Factory Method
有一个好方法可以避免强制用户使用 SmartPtrBuilder::CreateSharedPtr
来实例化您的类,从而不让用户负责正确指定所有地方需要传递的模板参数。您可以简单地为他们提供一个工厂方法,隐藏所有这些细节,如下所示:
class Derived : public Base, public EnableSharedFromThis< Derived >
{
public:
// Factory method
static std::shared_ptr< Derived > Create(int iValue)
{
return SmartPtrBuilder::CreateSharedPtr< Derived, Base >(new Derived(iValue));
}
void DerivedFunc()
{
FuncDerived(EnableSharedFromThis< Derived >::SharedFromThis());
}
private:
Derived(int iValue) : Base(iValue) {}
};
然后像这样实例化 Derived
:
// Simply use the factory method to instantiate Derived
std::shared_ptr< Derived > wDerived = Derived::Create(123);
其他想法
对于整个 enable_shared_from_this
问题,还存在另一种不错的解决方案,但它涉及 dynamic_cast
。可以为模板类 enable_shared_from_this
创建一个多态基类。weak_ptr
实际上位于多态基类内部,当调用 shared_from_this
方法时,会对多态基类的 weak_ptr
进行 dynamic_cast
,确保返回的 shared_ptr
具有正确的类型。但是,由于该解决方案涉及 dynamic_cast
,因此性能影响可能不容忽视。此外,还需要 RTTI,而在某些 RTOS 环境中,并非总是可以使用 RTTI,这使得此解决方案并非通用。顺便说一句,在这种情况下,使用 static_cast
而不是 dynamic_cast
并不总是能编译通过,例如,如果您使用虚继承;这就是为什么我们被迫使用 dynamic_cast
作为通用解决方案。
作为思考,如果有一种方法可以在编译时静态地检查或检查继承树,那么就可以实现一种解决方案,让您不必在构造 shared_ptr
时提供所有继承自 enable_shared_from_this
的类。但即使在理论上可以在 C++ 编译器中实现此功能,我认为在当前标准版本(文章撰写时为 C++11)中尚未实现。
此外,我还创建了一个 SmartPtrBuilder
的 可变参数模板 版本,如果您需要,请在下面的评论中提出或联系我。
历史
版本 1.0
zip 文件包含原始的 EnableSharedFromThis
模板类、SmartPtrBuilder
以及 VS2010 解决方案文件中的用法示例(您可以免费使用 VS2010 Express 在此)。对于 GCC,您只需构建示例并按以下方式运行:
$ g++ -std=c++0x main.cpp -o esft
$ ./esft