"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可以是标准布局;如果您这样做,某些类型的铸造将变得合法。我没有在 Adobe 代码中看到它。
在某些情况下,它可以完全与 vtable 分开分配(就像我在进行基于视图的类型擦除时所做的那样:我为传入类型创建了一个自定义 vtable ,并为其存储一个 void*
,然后将我的接口分派给所述自定义 vtable)。我没有看到在 Adobe 代码中使用它;但是作为对 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++ 类型,而在内部它们什么都不是。
最近,我遇到了几个使用 "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可以是标准布局;如果您这样做,某些类型的铸造将变得合法。我没有在 Adobe 代码中看到它。
在某些情况下,它可以完全与 vtable 分开分配(就像我在进行基于视图的类型擦除时所做的那样:我为传入类型创建了一个自定义 vtable ,并为其存储一个 void*
,然后将我的接口分派给所述自定义 vtable)。我没有看到在 Adobe 代码中使用它;但是作为对 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++ 类型,而在内部它们什么都不是。