C++:类特化是一个符合标准的编译器的有效转换?

Ste*_*Lin 18 c++ compiler-construction virtual-functions vtable compiler-optimization

希望这对StackOverflow的问题不太专业:如果是,可以在其他地方迁移,请告诉我......

很久以前,我写了一篇本科毕业论文提出了C++和相关语言的各种devirtualization技术,一般是根据代码路径(有点像模板)预编译专业化的想法,但与检查,以选择正确的专长是在情况下,运行时选择它们不能在编译时选择(因为模板必须是).

(非常)基本的想法类似于以下内容...假设你有一个类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
};
Run Code Online (Sandbox Code Playgroud)

而且你知道,存在的具体子类Foo一样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;
};
Run Code Online (Sandbox Code Playgroud)

并用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
}
Run Code Online (Sandbox Code Playgroud)

所以基本上,正在构造的对象的动态类型是根据其构造函数的参数的动态类型而改变的.(注意,您不能使用模板执行此操作,因为C<Foo>如果您知道f编译时的类型,则只能创建一个).从现在开始,任何FooA::bark()通过呼叫C::quack()只涉及一个虚拟呼叫:要么呼叫C::quack()静态绑定到动态呼叫的非专用版本FooA::bark(),要么呼叫C::quack()动态转发到C_FooA::quack()静态呼叫FooA::bark().此外,如果流量分析器有足够的信息进行静态调用C_FooA::quack(),在某些情况下可能会完全消除动态调度,如果它允许内联,这在紧密循环中非常有用.(虽然从技术上讲,即使没有这种优化,你也可能没问题......)

(请注意,这种转换是安全的,虽然不太有用,即使_f是非const并且受保护而不是私有,并且C是从不同的翻译单元继承的......为继承类创建vtable的翻译单元将不知道任何内容所有关于继承类的特化和构造函数都只是将它设置this->vptr为它自己的vtable,它不会引用任何专门的函数,因为它不会对它们有任何了解.)

这似乎需要付出很多努力才能消除一个级别的间接,但重点是你可以根据内部的本地信息将它做到任意嵌套级别(此模式后的任何深度的虚拟调用可以减少到一个)一个翻译单元,即使在你不了解的其他翻译单元中定义了新的类型,也能以一种有弹性的方式进行...你可能会添加许多代码膨胀,否则如果你没有天真地做了.

无论如何,独立于这种优化是否真的具有足够的爆炸效果值得实现的努力,并且还值得产生的可执行文件中的空间开销,我的问题是,标准C++中有什么可以防止编译器执行这样的转换?

我的感觉是否定的,因为标准根本没有说明虚拟调度是如何完成的,或者指示如何表示指向成员函数的指针.我非常确定RTTI机制没有任何关于防止CC_FooA伪装成所有目的的相同类型,即使它们具有不同的虚拟表.我能想到的另一件可能是重要的事情是对ODR的一些仔细阅读,但可能并非如此.

我忽略了什么吗?除了ABI /链接问题,这样的转换是否可以在不破坏符合C++程序的情况下实现?(此外,如果是的话,目前是否可以使用Itanium和/或MSVC ABI进行此操作?我很确定答案是肯定的,但希望有人可以确认.)

编辑:有没有人知道在C++,Java或C#的任何主流编译器/ JIT中是否实现了类似的东西?(请参阅以下评论中的讨论和链接聊天...)我知道JIT直接在呼叫站点进行虚拟化的推测性静态绑定/内联,但我不知道他们是否做了这样的事情(全新的vtables)基于在构造函数处完成的单个类型检查而不是在每个调用站点生成和选择.

asc*_*ler 1

标准 C++ 中是否有任何内容会阻止编译器执行此类转换?

如果您确定可观察的行为没有改变,则不会 - 这就是标准第 1.9 节中的“假设规则”。

但这可能会让证明你的转换是正确的变得相当困难:12.7/4:

当从构造函数(包括非静态数据成员的内存初始值设定项大括号或等于初始值设定项)或从析构函数直接或间接调用虚拟函数时,并且调用适用的对象是该对象在构造或析构过程中,调用的函数是在构造函数或析构函数自己的类或其基类之一中定义的函数,但不是在从构造函数或析构函数自己的类派生的类中重写它的函数,也不是在以下之一中重写它的函数:最派生对象的其他基类。

因此,如果析构函数Foo::~Foo()恰好直接或间接调用C::quack()一个对象c,其中 c._f指向被销毁的对象,则需要调用Foo::bark(),即使是在构造对象时_f也是如此。FooAc