C ++:类专门化一个合格的编译器的有效转换? [英] C++: Class specialization a valid transformation for a conforming compiler?

查看:154
本文介绍了C ++:类专门化一个合格的编译器的有效转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

希望这不是太专门的一个问题的StackOverflow:如果它是可以迁移到别处让我知道...

许多月前,我写了一本本科论文,提出了C ++和相关语言的各种虚拟化技术,通常基于代码路径的预编译专门化(有些像模板),但在运行时选择正确的专门化

Many moons ago, I wrote a undergraduate thesis proposing various devirtualization techniques for C++ and related languages, generally based on the idea of precompiled specialization of code paths (somewhat like templates) but with checks to choose the correct specializations are chosen at runtime in cases where they cannot be selected at compile-time (as templates must be).

(非常)基本的想法是这样的:假设你有一个class C 如下:

The (very) basic idea is something like the following...suppose you have a class C like the following:

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 的特殊版本,例如(注意不包括构造函数这里,有意,因为它不会被调用):

And you knew that there exist concrete subclasses of Foo like FooA, FooB, etc, with known complete types (without necessarily having an exhaustive list), then you could precompile specialized versions of C for some selected subclasses of Foo, like, for example (note the constructor is not included here, purposely, since it won't be called):

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 的构造函数替换为例如:

And replace the constructor of C with something like the following:

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
}


$ b b

因此,基本上,正在构造的对象的动态类型根据其构造函数的参数的动态类型而改变。 (注意,你不能用模板做这件事,因为你只能创建一个 C< Foo> 如果你知道 f 在编译时)。从现在起,对 FooA :: bark() C :: quack()的任何调用只涉及一个虚拟调用:调用 C :: quack()静态绑定到非专门版本,动态调用 FooA :: bark() / code>,或调用 C :: quack()动态转发到 C_Fo​​oA :: quack $ c>静态调用 FooA :: bark()。此外,在某些情况下,如果流分析器有足够的信息对 C_Fo​​oA :: quack()进行静态调用,这可能会非常有用,因此可以完全消除动态调度如果它允许内联的紧环。 (虽然从技术上来说,即使没有这个优化,你也许还是可以的。)

So basically, the dynamic type of the object being constructed is changed based on the dynamic type of the arguments to its constructor. (Note, you can't do this with templates because you can only create a C<Foo> if you know the type of f at compile-time). From now on, any call to FooA::bark() through C::quack() only involves one virtual call: either the call to C::quack() is statically bound to the non-specialized version which dynamically calls FooA::bark(), or the call to C::quack() is dynamically forwarded to C_FooA::quack() which statically calls FooA::bark(). Furthermore, dynamic dispatch might be eliminated completely in some cases if the flow analyzer has enough information to make a static call to C_FooA::quack(), which could be very useful in a tight loop if it allows inlining. (Although technically at that point you'd probably be OK even without this optimization...)

(注意,这种转换是安全的, code> _f 是非常量且受保护而不是私有, C 从不同的转换单元继承。创建继承类的vtable不会知道关于专门化的任何东西,并且继承类的构造函数只会将 this-> vptr 设置到它自己的vtable ,它不会引用任何专门的函数,因为它不会知道关于它们的任何东西。)

(Note that this transformation is safe, although less useful, even if _f is non-const and protected instead of private and C is inherited from a different translation unit...the translation unit creating the vtable for the inherited class won't know anything at all about the specializations and the constructor of the inherited class will just set the this->vptr to its own vtable, which will not reference any specialized functions because it won't know anything about them.)

这可能看起来很大的努力来消除一个间接级别,但是关键是你可以根据翻译单元内的本地信息,对任何任意嵌套级别(任何深度的虚拟调用跟随这个模式都可以减少为一个嵌套级别)进行处理,即使新类型是在你不知道的其他翻译单位中定义的...你可能会添加很多代码膨胀,如果你天真地做,你就不会有。

This might seem like a lot of effort to eliminate one level of indirection, but the point is that you can do it to any arbitrary nesting level (any depth of virtual calls following this pattern could be reduced to one) based only on local information within a translation unit, and do it in a way that's resilient even if new types are defined in other translation units that you don't know about...you just might add a lot of code bloat that you wouldn't have otherwise if you did it naively.

无论如何,独立于这种优化是否真的有足够的bang-for-the-buck值得实现的努力,也值得在产生的可执行文件的空间开销,我的问题是,在标准C ++中有什么可以防止编译器执行这样的转换?

Anyway, independent of whether this kind of optimization would really have enough bang-for-the-buck be worth the effort of implementation and also worth the space overhead in the resulting executable, my question is, is there anything in Standard C++ which would prevent a compiler from performing such a transformation?

我的感觉是没有,因为标准没有指定如何虚拟调度或者如何表示指向成员函数的指针。我确定没有什么关于RTTI机制防止 C C_Fo​​oA 从伪装为同一类型为所有目的,即使它们有不同的虚拟表。我可以想到的唯一其他的事情可能是重要的一些关于阅读的ODR,但可能不是。

My feeling is no, since the standard doesn't specify at all how virtual dispatch is done or how pointers-to-member-functions are represented. I'm pretty sure there's nothing about the RTTI mechanism preventing C and C_FooA from masquerading as the same type for all purposes, even if they have different virtual tables. The only other thing I could think of that could possibly matter is some close reading of the ODR, but probably not.

我可以俯瞰什么吗?除了ABI /链接问题,这样的转换是否可能不破坏符合C ++程序? (此外,如果是的话,目前可以使用安腾和/或MSVC ABI吗?我相信肯定的答案是肯定的,但希望有人可以确认。)

Am I overlooking something? Barring ABI/linking issues, would transformations like this be possible without breaking conforming C++ programs? (Furthermore, if yes, could this be done currently with the Itanium and/or MSVC ABIs? I'm fairly sure the answer there is yes, as well, but hopefully someone can confirm.)

EDIT :有人知道这样的东西是在任何主流编译器/ JIT的C ++,Java或C#中实现的吗? (看下面的评论讨论和链接聊天...)我知道JITs直接在调用站点做虚拟的推测静态绑定/内联,但我不知道他们做这样的事情(用全新的vtables

EDIT: Does anyone know if anything like this is implemented in any mainstream compiler/JIT for C++, Java, or C#? (See discussion and linked chat in the comments below...) I'm aware JITs do speculative static-binding/inlining of virtuals directly at call sites, but I don't know if they do anything like this (with entirely new vtables being generated and chosen based on a single type check done at the constructor, rather than at each call site).

推荐答案

$ b(在构造函数中执行单个类型检查,而不是在每个调用位置生成和选择) $ b

在标准C ++中有什么会阻止编译器执行这样的转换?

Is there anything in Standard C++ which would prevent a compiler from performing such a transformation?

确保可观察的行为是不变的 - 这是as-if规则,这是标准1.9节。

Not if you're sure the observable behavior is unchanged - that's the "as-if rule" which is Standard section 1.9.

但是这可能证明你的转换是相当困难的:12.7 / 4:

But this might make proving that your transformation is correct pretty difficult: 12.7/4:


当一个虚拟函数直接或间接地从构造函数调用(包括或非静态数据成员的 brace-or-equal-initializer )或来自析构函数,并且调用应用的对象是正在构建或销毁的对象,调用的函数是一个在构造函数或析构函数自己的类中或在它的一个基础中定义,而不是在从构造函数或析构函数自己的类派生的类中覆盖它的函数,或者在最导出的对象的其他基类中重写它。 / p>

When a virtual function is called directly or indirectly from a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's own class, or overriding it in one of the other base classes of the most derived object.

所以如果析构函数 Foo ::〜Foo()间接调用对象 c 上的 C :: quack(),其中 c._f 指向要销毁的对象,您需要调用 Foo :: bark(),即使 _f c 时创建 FooA

So if the destructor Foo::~Foo() happens to directly or indirectly call C::quack() on an object c, where c._f points at the object being destroyed, you need to call Foo::bark(), even if _f was a FooA when you constructed object c.

这篇关于C ++:类专门化一个合格的编译器的有效转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆