65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (5投票s)

2011 年 11 月 21 日

MIT

5分钟阅读

viewsIcon

48024

downloadIcon

397

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,您可以将 thisshared_ptr 传递给函数 func,确保您的程序中仅使用指向对象分配空间的 shared_ptr

enable_shared_from_this 模板类有一个指向 thisweak_ptr 成员,以及一个 shared_from_this 方法,该方法通过锁定该 weak_ptr 来返回指向 thisshared_ptrweak_ptr 的初始化方式是在 shared_ptr 构造函数中,通过一个巧妙的技巧来静态确定正在包装的对象是否继承自 enable_shared_from_this

问题在于,它假设只有一个 weak_ptr 需要初始化。如果 enable_shared_from_this 在继承树中出现不止一次,会怎样呢?如下所示:

esft1.png

在这种情况下,要么 enable_shared_from_thisweak_ptr 中的一个会被初始化,要么都不被初始化,具体取决于编译器(是的,是真的,不同的编译器行为不同!)。所以当一个 Derived 类型的对象尝试通过 Base(例如,在 Base 的某个方法中)和/或 Derived 的某个方法访问其 shared_from_this 时,即使该类已经被实例化为 shared_ptr,它也不会被初始化为 this。通常,人们会认为只要 Derived 是通过 shared_ptr 实例化的,shared_from_this 就总是被初始化的,但在那种情况下,事实并非如此,任何对其的访问都会导致段错误。

本文提出了一种初始化继承树中所有 enable_shared_from_thisweak_ptr 的解决方案。

背景

如果您在尝试间接调用 shared_from_this() 后遇到崩溃,并且您确定您的类对象都由 shared_ptr 指向,那么请检查类的继承树。如果在树中有超过一个 enable_shared_from_this,那么问题就在于此。

这发生在我身上,因为我实现了一个 SharedObserver 模式(使用 shared_ptr 的观察者模式)。在该模式中,我在 Subjectnotify 方法中通过 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());
  }
};

这是 FuncBaseFuncDerived 的实现,它们是两个自由函数,分别接收 BaseDerivedshared_ptr。它们分别在 BaseDerived 的成员函数内部被调用。这两个成员函数都将相应的 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
© . All rights reserved.