【问题标题】:avoid vtable mixup in c++ variadic template inheritance在 C++ 可变参数模板继承中避免 vtable 混淆
【发布时间】:2021-06-22 13:20:11
【问题描述】:

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

所以。一些基类:

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*-typed 的东西。我现在有很多看起来像这样的代码。从字面上看,这是我的程序的核心和关键部分:

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 会那样做。

【问题讨论】:

  • 你混合了静态和动态调度,现在两者都有缺点!
  • 如果你有一个basic* 的数据结构,那么你应该只对它们进行basic 操作。如果你想做has_wings的操作,那么你还应该有一个has_wings*的数据结构。
  • 这就是dynamic_cast 所做的。
  • dynamic_cast 甚至不会在这里编译,因为在 has_wingsbasic 之间没有可能的有效转换,尽管它可能适用于 basicentity&lt;has_wings&gt; - 检查。跨度>
  • 是的,所以它编译但抛出运行时异常,因为entity&lt;has_wings&gt; 不是duck 的超类

标签: c++ polymorphism variadic-templates c++20


【解决方案1】:

首先,您的模板什么也没有。 class duck : public basic, public has_wings, public has_legs 完全相同。

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

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

【讨论】:

  • 感谢您的回答!没错,在我的示例中,可变参数模板似乎毫无意义;然而,它在扩展版本中具有非常美观的目的。不幸的是,在尝试创建问题的最小表示时,这个目的已经丢失。那么,您认为我的问题没有解决方案,因为我的问题是我疯了,我说得对吗? (比喻……)
  • > 它必须已经定义了您要访问的所有虚拟设备,这似乎是关键点。我以为有办法解决它。我会等几个小时,然后可能会接受你的回答。
【解决方案2】:

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

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

【讨论】:

  • 好答案,我会尝试让接口虚拟继承基本的方法,谢谢!
猜你喜欢
  • 2015-11-10
  • 2013-02-09
  • 1970-01-01
  • 2016-04-20
  • 2021-10-17
  • 2016-06-20
  • 2012-09-20
  • 2013-03-20
  • 2014-05-15
相关资源
最近更新 更多