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

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

最近,我遇到了几个使用 "hand-rolled" 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 不太满意自己解决这个问题。

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

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

还有一个"class"带手卷的vtable可以是标准布局;如果您这样做,某些类型的铸造将变得合法。我没有在 Adob​​e 代码中看到它。

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

如果您有一个手动滚动的 vtable,您可以创建动态 classes,您可以在其中分配一个 vtable 条目并将其指针设置为您选择的任何内容(可能以编程方式).如果您有 10 个方法,每个方法都可以处于 10 种状态之一,则需要 10^10 个不同的 classes 和正常的 vtables。使用手动 vtable,您只需要在 table 某处管理每个 classes 的生命周期(因此实例不会比 class 长)。

举个例子,我可以采用一种方法,并在 的特定 实例 上向其添加 "run before" 或 "run after" 方法=44=](仔细的生命周期管理),或者在 class.

的每个实例上

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

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

在 C++ 出现之前,OO 模式就存在于 C 中。 C++ 简单地选择了一些合理的选项;当你回到伪 C 风格的手册 OO 时,你可以访问那些替代选项。您可以(用胶水)修饰事物,使它们 看起来 对于临时用户来说像普通的 C++ 类型,而在内部它们什么都不是。