繁体   English   中英

有没有办法防止 vtable 在 C++ 中发出?

[英]Is there a way to prevent the vtable from being emitted in c++?

我正在制作一个库,它要求类必须继承其他类才能执行特定操作。 然而,这不是简单的多态性。 这些类是虚函数的代码生成器,没有数据并且依赖于CRTP,因此它们本身不需要vtable。

有没有办法阻止为这些类发出 vtable? 我假设虚函数指针将传递给派生类,而虚析构函数将跳过这些类。 有点像将这些类融合在一起。

如果在 C++ 域中没有任何通用内容,那么可能特定于 clang、gcc 和 vc?

例子:

#include<iostream>

template <typename D, typename B>
struct jelly : B
{
  virtual void do_stuff() { static_cast<D*>(this)->D::do_some_other_stuff(); }
};

template <typename D>
struct jelly<D, void>
{
  virtual void do_stuff() { static_cast<D*>(this)->D::do_some_other_stuff(); }
};

struct A : jelly<A, void>
{
  void do_some_other_stuff() { std::cout << "A::do_some_other_stuff()\n"; }
};

struct B : jelly<B, A>
{
  void do_some_other_stuff() { std::cout << "B::do_some_other_stuff()\n"; }
};

int main()
{
  A a;
  a.do_stuff();  // output: A::do_some_other_stuff()
  B b;
  b.do_stuff();  // output: B::do_some_other_stuff()
  A& aa = b;
  aa.do_stuff(); // output: B::do_some_other_stuff()
}

只是为了澄清,这只是一个例子。 它确实运行了,但jelly代表的类别数量实际上是 3 个不同的类别。 一个是由开发人员使用jelly库显式继承的,另外两个是隐式完成的,然后再继承回开发人员自己的类。 正是因为班级的数量会增加 3 倍,这才让我感到担忧,这也是我问这个问题的原因。

如果您将成员函数声明为virtual ,则该类必须具有实现认为必要的任何机制来完成 C++ 要求virtual函数执行的操作。 但这意味着该类型现在是多态的,这要求该类型能够做 C++ 要求的多态类型可以做的事情。 具体来说, typeiddynamic_cast

这很重要。 从多态类型派生的类本身就是多态的,无论它是否覆盖任何virtual函数。 这意味着您必须能够从该类的实例中获取类型信息。 你是否真的这样做无关紧要; 你可以,因此必须存在机器来允许它。

对于 vtable 实现,这通常意味着每个多态类型都需要有一个唯一的vtable 对象。 除了指向虚函数的指针之外,vtable 还会有一个索引或指向某些类型特定信息的指针。 由于 vtable 往往非常小,因此拥有另一个 vtable 并不是特别繁重。 实际上,类型标识信息本身通常比 vtable 更重要。

现在,编译器具有允许您删除运行时类型识别的所有痕迹的选项。 具体来说, typeid不再起作用,并且dynamic_cast永远不会抛出,因此不需要验证强制转换,编译器不再需要为A提供与jelly<A>不同的 vtable。 但是, 该功能的主要目标是type_info对象和其他标识信息的表 所以我不能谈论这个特性对 vtable 生成的影响。

然而,最终,你无能为力。 这些类可能会得到 vtables,就是这样。

我唯一知道的编译器扩展是 MSVC 的__declspec(novtable)

这种形式的__declspec可以应用于任何类声明,但只能应用于纯接口类,即永远不会自己实例化的类。 __declspec阻止编译器生成代码来初始化类的构造函数和析构函数中的 vfptr。 在许多情况下,这会删除与该类关联的对 vtable 的唯一引用,因此链接器将删除它。 使用这种形式的__declspec可以显着减少代码大小。

如果您尝试实例化标有novtable的类,然后访问类成员,您将收到访问冲突 (AV)。

当您使用 MSVC 的__interface关键字时,该修饰符是隐含的。

你做:

template <typename D, typename B>
struct jelly : B
{
  virtual void do_stuff() { D::do_some_other_stuff(); }
};

然后你做:

struct B : jelly<B, A>
{
  void do_some_other_stuff() { std::cout << "B::do_some_other_stuff()\n"; }
};

这意味着struct B取决于jelly<B, A> 那,反过来需要调用B (又名D )函数do_some_other_stuff ,但B尚未定义。 也许您需要将void do_stuff移到类声明之外。 无论如何,当您将其声明为virtual ,它不应该被内联。

另外,您正在使用

struct A : jelly<A, void>

翻译成:

struct jelly : void
{
  virtual void do_stuff() { A::do_some_other_stuff(); }
};

你希望如何从void继承?

样式说明:不要使用struct只是为了避免public 当应该有一个class (因为你正在继承)而不是任何typename时,不要使用typename D 不要使用名称 A、B、C、D,输入一些更具描述性的名称。 struct jelly : B是从typename B还是从struct B继承?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM