"hand-rolled" vtable 方法的优势是什么?

What's the advantage of the "hand-rolled" vtable approach?

本文关键字:是什么 hand-rolled vtable 方法      更新时间:2023-10-16

最近,我遇到了几个使用"手动"vtable的类型擦除实现——Adobe ASL的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不够熟悉,无法自己解决这个问题。

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

浏览一下有问题的来源,有很多关于各种结构的大小的断言(因为在一种情况下,它们需要放入两个替身的数组中)。

此外,一个"类"与一个手卷vtable可以是标准布局;如果你这样做,某些类型的铸造就合法了。我不认为这在Adobe代码中使用。

在某些情况下,它可以完全与vtable分开分配(就像我在执行基于视图的类型擦除时所做的那样:我为传入类型创建一个自定义vtable,并为其存储一个void*,然后将我的接口分派给所述自定义vtable)。我不认为这在Adobe代码中使用;但是充当对CCD_ 8的伪引用的CCD_。我将它用于can_construct<T>sink<T>function_view<Sig>甚至move_only_function<Sig>(所有权由unique_ptr处理,通过具有1个条目的本地vtable进行操作)。

如果你有一个手动的vtable,你可以创建动态类,在那里你可以分配一个vtable条目,并将它的指针设置为你选择的任何东西(可能是通过编程)。如果你有10个方法,每个方法都可以处于10种状态中的一种,那就需要10^10个具有普通vtables的不同类。使用手动vtable,您只需要在某个表中管理每个类的生存期(这样实例就不会比该类更长寿)。

举个例子,我可以在一个类的特定实例上(使用谨慎的生存期管理),或者在该类的每个实例上,取一个方法,并向其添加一个"run before"或"run after"方法。

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

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

OO模式在C++出现之前就存在于C中。C++简单地选择了几个合理的选项;当您回到伪C风格的手动OO时,您可以访问这些替代选项。你可以(用胶水)把它们打扮得像普通的C++类型,而在内部则不然。