在 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.
}
解决方案
- 让每个组件(也就是纯接口)和
entity
-class 实际上继承自 basic
- 在
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_wings
、fly
)你需要 dynamic_cast
s 的接口到达正确的动态类型(你的 reinterpret_cast
的例子是错误的,你不能使用 reinterpret_cast
在 class 层次结构中移动)是一个写得不好的接口。
有时可以使用访问者模式,但在我看来,它往往会产生极难排除故障的代码。
您必须使用 static_cast
或 dynamic_cast
在继承层次结构中移动,并且 static_cast
不能下降虚拟继承或交叉转换(在一个步骤中),因为从源类型和目标类型派生的不同 类 的布局可能不同。实际上,您必须知道 actual 类型(不仅仅是它具有给定的方面)才能进行转换。如果你有你的“方面” 类 从 basic
继承——实际上,所以有一个独特的 basic*
——你可以使用 dynamic_cast
不是为了它的 检查 目的,而是为了为相关方面找到合适的 vtable。
如果您负担不起,您可能希望以某种方式合并 接口。然后你必须小心,只有当它们有意义时才调用函数;已经是这种情况(“通过魔法”),但是普通的调用语法可能是一个有吸引力的麻烦。您也可以尝试一些 C 风格的多态性,使用手动创建的 vtable,每个可选行为具有 函数指针 。
我有一个想法,可以通过可变参数模板继承以更好的方式构建 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.
}
解决方案
- 让每个组件(也就是纯接口)和
entity
-class 实际上继承自basic
- 在
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_wings
、fly
)你需要 dynamic_cast
s 的接口到达正确的动态类型(你的 reinterpret_cast
的例子是错误的,你不能使用 reinterpret_cast
在 class 层次结构中移动)是一个写得不好的接口。
有时可以使用访问者模式,但在我看来,它往往会产生极难排除故障的代码。
您必须使用 static_cast
或 dynamic_cast
在继承层次结构中移动,并且 static_cast
不能下降虚拟继承或交叉转换(在一个步骤中),因为从源类型和目标类型派生的不同 类 的布局可能不同。实际上,您必须知道 actual 类型(不仅仅是它具有给定的方面)才能进行转换。如果你有你的“方面” 类 从 basic
继承——实际上,所以有一个独特的 basic*
——你可以使用 dynamic_cast
不是为了它的 检查 目的,而是为了为相关方面找到合适的 vtable。
如果您负担不起,您可能希望以某种方式合并 接口。然后你必须小心,只有当它们有意义时才调用函数;已经是这种情况(“通过魔法”),但是普通的调用语法可能是一个有吸引力的麻烦。您也可以尝试一些 C 风格的多态性,使用手动创建的 vtable,每个可选行为具有 函数指针 。