"hand-rolled" vtable 方法的优势是什么?
What's the advantage of the "hand-rolled" vtable approach?
最近,我遇到了几个使用"手动"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++类型,而在内部则不然。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- C++避免重复声明的语法是什么
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- C++中名称篡改的目的是什么
- 在 c++ 中拥有一组结构的正确方法是什么?
- 这个指针和内存代码打印是什么?我不知道是打印垃圾还是如何打印我需要的值
- 是什么阻止DOMTimerCoordinator::NextID进入无休止的循环
- 派生类销毁的最佳实践是什么
- 这个语法std::class<>{}(arg1, arg2) 在C++中是什么意思?
- 通过JNI传递数据数组的最快方法是什么
- "using namespace std;"在C++的作用是什么?
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 文件系统:复制功能的速度秘诀是什么
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 是什么原因导致它无法编译?它是声明签名还是在函数本身的实现中
- "hand-rolled" vtable 方法的优势是什么?