我希望这个问题不会太过专业,如果是的话,请告诉我是否需要迁移到其他地方...
很久以前,我写了一篇本科论文,提出了各种针对C++和相关语言的去虚拟化技术,通常基于代码路径的预编译特化思想(有点像模板),但在无法在编译时选择正确的特化情况下(如必须使用模板),需要在运行时进行检查来选择正确的特化。
(非常)基本的想法如下...假设你有一个类
如果您知道存在具有已知完整类型(不一定需要详尽列表)的
基本上,正在构造的对象的动态类型会根据其构造函数的参数的动态类型而改变。(请注意,您无法使用模板执行此操作,因为只有在编译时知道f的类型才能创建C )。从现在开始,通过C :: quack()调用FooA :: bark()的任何调用都只涉及一个虚拟调用:要么对C :: quack()的调用静态地绑定到非专门化版本,该版本动态调用FooA :: bark(),或者对C :: quack()的调用被动态转发到C_FooA :: quack(),后者静态调用FooA :: bark()。此外,在某些情况下,如果流分析器具有足够的信息以对C_FooA :: quack()进行静态调用,则可以完全消除动态调度,如果允许内联,则在紧密循环中非常有用。(尽管在那时,在没有此优化的情况下,您可能已经没问题了...)
(请注意,即使“_f”是非const和受保护的,而不是私有的,并且“C”是从不同的翻译单元继承的,这种转换也是安全的,尽管不太有用...创建继承类的vtable的翻译单元将对特化一无所知,继承类的构造函数将只设置“this->vptr”为其自己的vtable,它不会引用任何特殊的函数,因为它对它们一无所知。)
这可能看起来需要很多工作才能消除一级间接性,但重点是您可以根据翻译单元内的本地信息将其应用于任意嵌套级别(遵循此模式的任何虚拟调用深度都可以减少到一级),并以一种弹性方式执行,即使在其他翻译单元中定义了您不知道的新类型...如果您天真地执行它,您可能会添加许多代码膨胀,否则您将不会有。
无论如何,“独立于这种优化是否真正具有足够的效果,值得实施的努力以及结果可执行文件中的空间开销是否值得”的问题是,标准C ++中是否有任何东西会阻止编译器执行此类转换?
我的感觉是不行的,因为标准并没有指定虚拟分派的实现方式或成员函数指针的表示方式。我相信RTTI机制对于C和C_FooA伪装成同一类型并不会有任何影响,即使它们有不同的虚拟表。唯一可能有影响的是ODR的细节,但可能性很小。
除了ABI/链接问题外,如果不破坏符合C++标准的程序,是否可以进行此类转换?(此外,如果可以,目前能否使用Itanium和/或MSVC ABI进行?我相当确定答案也是肯定的,但希望有人能确认。)
编辑:是否有人知道C++、Java或C#中的任何主流编译器/JIT是否实现了类似的功能?(请参见下面评论中的讨论和链接聊天记录...)我知道JIT在调用点直接进行虚拟静态绑定/内联,但我不知道它们是否像这样做(通过在构造函数中进行单个类型检查来生成全新的虚拟表并进行选择,而不是在每个调用点上进行)。
很久以前,我写了一篇本科论文,提出了各种针对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
的具体子类,例如FooA
,FooB
等,则可以预编译某些选定的Foo
子类的专门版本的C
,例如(请注意,构造函数在此处故意未包含,因为它不会被调用):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
}
基本上,正在构造的对象的动态类型会根据其构造函数的参数的动态类型而改变。(请注意,您无法使用模板执行此操作,因为只有在编译时知道f的类型才能创建C )。从现在开始,通过C :: quack()调用FooA :: bark()的任何调用都只涉及一个虚拟调用:要么对C :: quack()的调用静态地绑定到非专门化版本,该版本动态调用FooA :: bark(),或者对C :: quack()的调用被动态转发到C_FooA :: quack(),后者静态调用FooA :: bark()。此外,在某些情况下,如果流分析器具有足够的信息以对C_FooA :: quack()进行静态调用,则可以完全消除动态调度,如果允许内联,则在紧密循环中非常有用。(尽管在那时,在没有此优化的情况下,您可能已经没问题了...)
(请注意,即使“_f”是非const和受保护的,而不是私有的,并且“C”是从不同的翻译单元继承的,这种转换也是安全的,尽管不太有用...创建继承类的vtable的翻译单元将对特化一无所知,继承类的构造函数将只设置“this->vptr”为其自己的vtable,它不会引用任何特殊的函数,因为它对它们一无所知。)
这可能看起来需要很多工作才能消除一级间接性,但重点是您可以根据翻译单元内的本地信息将其应用于任意嵌套级别(遵循此模式的任何虚拟调用深度都可以减少到一级),并以一种弹性方式执行,即使在其他翻译单元中定义了您不知道的新类型...如果您天真地执行它,您可能会添加许多代码膨胀,否则您将不会有。
无论如何,“独立于这种优化是否真正具有足够的效果,值得实施的努力以及结果可执行文件中的空间开销是否值得”的问题是,标准C ++中是否有任何东西会阻止编译器执行此类转换?
我的感觉是不行的,因为标准并没有指定虚拟分派的实现方式或成员函数指针的表示方式。我相信RTTI机制对于C和C_FooA伪装成同一类型并不会有任何影响,即使它们有不同的虚拟表。唯一可能有影响的是ODR的细节,但可能性很小。
除了ABI/链接问题外,如果不破坏符合C++标准的程序,是否可以进行此类转换?(此外,如果可以,目前能否使用Itanium和/或MSVC ABI进行?我相当确定答案也是肯定的,但希望有人能确认。)
编辑:是否有人知道C++、Java或C#中的任何主流编译器/JIT是否实现了类似的功能?(请参见下面评论中的讨论和链接聊天记录...)我知道JIT在调用点直接进行虚拟静态绑定/内联,但我不知道它们是否像这样做(通过在构造函数中进行单个类型检查来生成全新的虚拟表并进行选择,而不是在每个调用点上进行)。
C_FooX
,但是您随后静态调用正确的FooX
。或者您静态调用C
,它会动态地为您获取正确的FooX
。两个虚拟调用而不是一个,并且只要类型信息在当前TU中,您可以将其嵌套到任意层级,并且它对其他TU中添加的附加类型具有弹性(因此您不需要整个程序分析)。 - Stephen Lin