在 C++ 可变参数模板继承中避免 vtable 混淆

avoid vtable mixup in c++ variadic template inheritance

我有一个想法,可以通过可变参数模板继承以更好的方式构建 class 实体组件。这个问题源于 3d 图形背景下的时髦实验,但我相信我已经将它分解为一个关于 C++ 的非常抽象的问题。我能够在 Microsoft cl aka 当前实现的范围内使用 C++20。 MSVC++19-工具链。

所以。几个 Base classes:

class basic {
public:
  std::wstring get_name() { return name; }
  virtual void do_something_idk_virtual() = 0;
  virtual ~basic() {}
private:
  std::wstring name;
}
class has_legs {
public:
  virtual void walk() = 0;
  virtual ~has_legs() {}
}
class has_wings {
public:
  virtual void fly() = 0;
  virtual ~has_wings() {}
}
template<typename... Ts>
class entity : public basic, public Ts... {
public:
  virtual ~entity() {}
}

到目前为止,还不错。现在我想做一只鸭子:

class duck : entity<has_wings, has_legs> {
public:
  virtual ~duck() {}
  virtual void walk() { cout << "walk" << endl; }
  virtual void fly() { cout << "fly" << endl; }
  virtual void do_something_idk_virtual() { } // nothing,
}

仍然,似乎有效。问题是:我知道有数据结构(比如 linked_list,或某种图形),我使用访问者模式来处理 basic* 类型的东西。我现在有很多看起来像这样的代码。毫不夸张地说,这是我的程序的核心和关键部分:

void visit(basic* node) {
  //here i find out, through magic or some other kind of out-of-scope-mechanism that node is at least a has_wings. Problem:

  reinterpret_cast<has_wings*>(node)->fly(); //does not work, will call basic::do_something_idk_virtual(). As far as i understand, this is because the compiler-generated vtable does not change via the reinterpret_cast.
  reinterpret_cast<entity<has_wings>*>(node)->fly(); //might, work, problems start to come in if node is of some type that has_wings and has_legs. It sometimes calls some other method, depending on the ordering in declaring the class.
}

解决方案

  1. 让每个组件(也就是纯接口)和 entity-class 实际上继承自 basic
  2. basic中添加非虚方法:
template<typename TComponent> TComponent* get_component() { 
return dynamic_cast<TComponent*>(this); 
}

这将修复 vtables。我不确定为什么 dynamic_cast 会那样做。

首先,你的模板没有给你任何东西。 class duck : public basic, public has_wings, public has_legs完全相同。

其次,您需要确定您的多态访问级别。如果你的级别是 basic,那么它必须已经定义了你想要访问的所有虚拟(即 has_wingsfly)你需要 dynamic_casts 的接口到达正确的动态类型(你的 reinterpret_cast 的例子是错误的,你不能使用 reinterpret_cast 在 class 层次结构中移动)是一个写得不好的接口。

有时可以使用访问者模式,但在我看来,它往往会产生极难排除故障的代码。

您必须使用 static_castdynamic_cast 在继承层次结构中移动,并且 static_cast 不能下降虚拟继承或交叉转换(在一个步骤中),因为从源类型和目标类型派生的不同 类 的布局可能不同。实际上,您必须知道 actual 类型(不仅仅是它具有给定的方面)才能进行转换。如果你有你的“方面” 类 basic 继承——实际上,所以有一个独特的 basic*——你可以使用 dynamic_cast 不是为了它的 检查 目的,而是为了为相关方面找到合适的 vtable。

如果您负担不起,您可能希望以某种方式合并 接口。然后你必须小心,只有当它们有意义时才调用函数;已经是这种情况(“通过魔法”),但是普通的调用语法可能是一个有吸引力的麻烦。您也可以尝试一些 C 风格的多态性,使用手动创建的 vtable,每个可选行为具有 函数指针