【问题标题】:What's the advantage of the "hand-rolled" vtable approach?“手卷” vtable 方法的优势是什么?
【发布时间】:2015-11-02 19:47:05
【问题描述】:

最近,我遇到了几个使用“手动”vtable 的类型擦除实现 - Adobe ASL's any_regular_t 就是一个例子,尽管我也看到它在 Boost ASIO 中使用过(为了完成例行队列)。

基本上,父类型被传递一个指向静态类型的指针,该静态类型充满了子类型中定义的函数指针,类似于下面...

struct parent_t;

struct vtbl {
  void (*invoke)(parent_t *, std::ostream &);
};

struct parent_t {
  vtbl *vt;
  parent_t(vtbl *v) : vt(v) { }
  void invoke(std::ostream &os) {
    vt->invoke(this, os);
  }
};

template<typename T>
struct child_t : parent_t {
  child_t(T val) : parent_t(&vt_), value_(val) { }
  void invoke(std::ostream &os) {
    // Actual implementation here
    ...
  }
private:
  static void invoke_impl(parent_t *p, std::ostream &os) {
    static_cast<child_t *>(p)->invoke(os);
  }
  T value_;
  static vtbl vt_;
};

template<typename T>
vtbl child_t<T>::vt_ = { &child_t::invoke_impl };

我的问题是,这个成语有什么好处?据我所知,这只是对编译器免费提供的内容的重新实现。当parent_t::invoke 调用vtbl::invoke 时,是否还会有额外间接的开销。

我猜这可能与编译器能够内联或优化对 vtbl::invoke 的调用有关,但我对 Assembler 无法解决这个问题感到不舒服我自己。

【问题讨论】:

  • 在实现 C 对象时需要它(不使用 C++ 对象)
  • @DieterLücking 你的意思是便携性?它似乎在仅限 C++ 的库中也很流行
  • 另一个原因:通过(诚然不平凡的)修改,您可以创建一个虚函数调用 matrix 而不是简单的 vtable,以实现 multiple dispatch。它有点令人费解,但 Stroustrup 与人合着了一系列论文,概述了可能的实现。
  • 在 C++ 中仅使用 C 功能允许您制作兼容的库,即使不存在 C++ ABI。 (您定义 ABI。)

标签: c++ type-erasure vtable


【解决方案1】:

具有有用 vtable 的类基本上要求它是动态分配的。虽然你可以做一个固定的存储缓​​冲区并在那里分配,但这很麻烦;一旦你去virtual,你就无法合理控制实例的大小。使用手动 vtable 即可。

看看有问题的来源,有很多关于各种结构大小的断言(因为它们需要在一种情况下适合两个双精度数的数组)。

此外,带有手动 vtable 的“类”可以是标准布局;如果你这样做,某些类型的铸造就变得合法了。我没有在 Adob​​e 代码中看到它。

在某些情况下,它可以完全与 vtable 分开分配(就像我在执行基于视图的类型擦除时所做的那样:我为传入类型创建一个自定义 vtable,并为其存储一个 void*,然后调度我对自定义 vtable 的界面)。我没有看到它在 Adob​​e 代码中使用;但是作为any_regular 的伪引用的any_regular_view 可能会使用这种技术。我将它用于can_construct&lt;T&gt;sink&lt;T&gt;function_view&lt;Sig&gt; 甚至move_only_function&lt;Sig&gt;(所有权由unique_ptr 处理,通过具有 1 个条目的本地 vtable 进行操作)。

如果您有一个手动滚动的 vtable,您可以创建动态类,您可以在其中分配一个 vtable 条目并将其指针设置为您选择的任何内容(可能以编程方式)。如果您有 10 种方法,每种方法都可以处于 10 种状态之一,那将需要 10^10 个具有普通 vtable 的不同类。使用手动滚动的 vtable,您只需在某个表中管理每个类的生命周期(因此实例不会超过类)。

例如,我可以采用一个方法,并在类的特定实例上添加一个“运行前”或“运行后”方法(小心地进行生命周期管理),或在该类的每个实例上。

生成的 vtable 也有可能在各个方面都比编译器生成的 vtable 更简单,因为它们没有那么强大。例如,编译器生成的 vtable 处理虚拟继承和动态转换。除非使用,否则虚拟继承情况可能没有开销,但动态转换可能需要开销。

您还可以控制初始化。使用编译器生成的 vtable,表的状态按照标准的要求定义(或未定义):使用手动滚动的 vtable,您可以确保您选择的任何不变量保持不变。

在 C++ 出现之前,C 中就存在 OO 模式。 C++ 只是选择了几个合理的选项;当您回到伪 C 风格手册 OO 时,您可以访问这些替代选项。您可以(用胶水)修饰一些东西,使它们看起来对普通用户来说就像普通的 C++ 类型,而它们的内部却不是。

【讨论】:

  • 啊,是的。我从来没有考虑过布局。谢谢。我似乎记得看到过一些使用类似“vtables”来实现 COM 对象的 C 代码
猜你喜欢
  • 2014-08-03
  • 2015-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-31
  • 1970-01-01
  • 1970-01-01
  • 2011-03-25
相关资源
最近更新 更多