希望这对于 StackOverflow 来说不是太专业的问题:如果它是并且可以迁移到其他地方,请告诉我......
很多个月前,我写了一篇本科论文,提出了 C++ 和相关语言的各种去虚拟化技术,通常基于代码路径的预编译特化(有点像模板)的想法,但在运行时选择正确的特化进行检查,以防出现以下情况它们不能在编译时选择(因为必须是模板)。
(非常)基本的想法类似于以下内容......假设您有一个C
类似以下的类:
class C : public SomeInterface
{
public:
C(Foo * f) : _f(f) { }
virtual void quack()
{
_f->bark();
}
virtual void moo()
{
quack(); // a virtual call on this because quack() might be overloaded
}
// lots more virtual functions that call virtual functions on *_f or this
private:
Foo * const _f; // technically doesn't have to be const explicitly
// as long as it can be proven not be modified
};
并且您知道存在具有已知完整类型的Foo
like FooA
、FooB
等的具体子类(不一定有详尽的列表),然后您可以C
为某些选定的子类预编译 的专用版本Foo
,例如(请注意,构造函数不是故意包括在这里,因为它不会被调用):
class C_FooA final : public SomeInterface
{
public:
virtual void quack() final
{
_f->FooA::bark(); // non-polymorphic, statically bound
}
virtual void moo() final
{
C_FooA::quack(); // also static, because C_FooA is final
// _f->FooA::bark(); // or you could even do this instead
}
// more virtual functions all specialized for FooA (*_f) and C_FooA (this)
private:
FooA * const _f;
};
并将构造函数替换为C
以下内容:
C::C(Foo * f) : _f(f)
{
if(f->vptr == vtable_of_FooA) // obviously not Standard C++
this->vptr = vtable_of_C_FooA;
else if(f->vptr == vtable_of_FooB)
this->vptr = vtable_of_C_FooB;
// otherwise leave vptr unchanged for all other values of f->vptr
}
所以基本上,正在构造的对象的动态类型会根据其构造函数的参数的动态类型而改变。(请注意,您不能使用模板执行此操作,因为您只能在编译时C<Foo>
知道类型的情况下创建 a)。f
从现在开始,任何对FooA::bark()
through的调用C::quack()
都只涉及一个虚拟调用:或者调用C::quack()
静态绑定到动态调用的非专业版本FooA::bark()
,或者调用C::quack()
动态转发到C_FooA::quack()
静态调用FooA::bark()
。此外,如果流分析器有足够的信息进行静态调用,动态调度可能会在某些情况下完全消除。C_FooA::quack()
,如果它允许内联,这在紧密循环中可能非常有用。(虽然从技术上讲,即使没有这种优化,你也可能没问题......)
(请注意,这种转换是安全的,尽管用处不大,即使_f
是非常量且受保护而不是私有的并且C
是从不同的翻译单元继承的……为继承的类创建 vtable 的翻译单元在关于继承类的特化和构造函数的所有内容都将设置this->vptr
为它自己的 vtable,它不会引用任何专门的函数,因为它不知道任何关于它们的信息。)
这似乎需要付出很多努力来消除一个间接级别,但关键是您可以仅基于内部的本地信息对任何任意嵌套级别(遵循此模式的虚拟调用的任何深度都可以减少到一个)执行此操作一个翻译单元,并以一种有弹性的方式执行它,即使在您不知道的其他翻译单元中定义了新类型......您可能会添加很多您不会有的代码膨胀,否则如果您天真地做了。
无论如何,不管这种优化是否真的有足够的物有所值,值得努力实现,也值得在生成的可执行文件中占用空间,我的问题是,标准 C++ 中是否有任何东西可以防止执行这种转换的编译器?
我的感觉是否定的,因为该标准根本没有指定如何完成虚拟调度或如何表示指向成员函数的指针。我很确定 RTTI 机制没有任何内容可以防止C
和C_FooA
伪装成相同类型的所有用途,即使它们有不同的虚拟表。我能想到的唯一可能重要的另一件事是仔细阅读 ODR,但可能不是。
我忽略了什么吗?除非 ABI/链接问题,这样的转换是否可以在不破坏符合 C++ 程序的情况下进行?(此外,如果是的话,目前是否可以使用 Itanium 和/或 MSVC ABI 来完成?我很确定答案也是肯定的,但希望有人可以确认。)
编辑:有谁知道在 C++、Java 或 C# 的任何主流编译器/JIT 中是否实现了类似的东西?(请参阅下面评论中的讨论和链接聊天...)我知道 JIT 直接在呼叫站点进行虚拟的推测性静态绑定/内联,但我不知道他们是否做了类似的事情(使用全新的 vtables基于在构造函数而不是在每个调用站点完成的单个类型检查来生成和选择)。