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

时间:2013-03-01 00:38:16

标签: c++ compiler-construction compiler-optimization virtual-functions vtable

希望这对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的具体子类,如FooAFooB等,具有已知的完整类型(不一定有详尽的列表),那么你可以预编译专门的某些选定的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>的类型,则只能创建f。从现在开始,对FooA::bark()C::quack()的任何调用只涉及一个虚拟调用:对C::quack()的调用静态绑定到动态调用FooA::bark()的非专用版本,或者C::quack()的来电被动态转发到静态调用C_FooA::quack()的{​​{1}}。此外,如果流量分析器具有足够的信息来对FooA::bark()进行静态调用,则在某些情况下可能会完全消除动态调度,如果它允许内联,这在紧密循环中非常有用。 (虽然从技术上讲,即使没有这种优化,你也可能没事......)

(请注意,此转换是安全的,尽管不太有用,即使C_FooA::quack()是非const并且受保护而非私有,_f也是从不同的翻译单元继承...翻译单元为继承的类创建vtable根本不会知道任何关于特化的内容,继承类的构造函数只会将C设置为它自己的vtable,它不会引用任何专用函数,因为它不会了解他们。)

这似乎需要付出很多努力来消除一个级别的间接,但关键是你可以在任何嵌套级别(任何深度的虚拟调用都可以减少到一个)的基础上实现翻译单元中的本地信息,即使在您不了解的其他翻译单元中定义了新类型,也能以有弹性的方式执行...您可能会添加许多您不具备的代码膨胀否则,如果你天真地做了。

无论如何,独立于这种优化是否真的具有足够的优势,值得付出实施的努力,还值得产生的可执行文件中的空间开销,我的问题是,标准C ++中是否存在阻止编译器执行此类转换的内容?

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

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

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

2 个答案:

答案 0 :(得分:1)

  

标准C ++中是否存在阻止编译器执行此类转换的内容?

如果你确定可观察的行为没有改变,那就不是 - 这就是标准第1.9节的“as-if规则”。

但这可能会证明你的转变是非常困难的:12.7 / 4:

  

直接或间接从构造函数(包括 mem-initializer brace-or-equal-initializer )为非静态数据成员调用虚函数时)或者来自析构函数,并且调用适用的对象是正在构造或销毁的对象,被调用的函数是在构造函数或析构函数自己的类或其基础中定义的函数,但不是覆盖它的函数。从构造函数或析构函数自己的类派生的类,或者在最派生对象的其他基类之一中覆盖它。

因此,如果析构函数Foo::~Foo()恰好在对象C::quack()上直接或间接调用c,其中c._f指向要销毁的对象,则需要调用{ {1}},即使构建对象Foo::bark()_fFooA

答案 1 :(得分:0)

在第一次阅读时,这听起来像是一个以c ++为中心的polymorphic inline caching变体。我认为V8和Oracle的JVM都使用它,我知道.NET does

回答你原来的问题:我不认为标准中有任何禁止这些实现的内容。 C ++非常重视“原样”规则;只要你忠实地实现正确的语义,你就可以以任何你喜欢的疯狂方式实现。 c ++虚拟调用并不是很复杂,所以我怀疑你是否会遇到任何边缘情况(不像是,例如,你试图通过 static 绑定做一些聪明的事情)。