3

我有一个具有两个虚拟成员函数的类:foowrapper. foo短而快,并且包含一个多次wrapper调用的循环。foo我希望有一些方法可以内联对foo 包装函数内部的调用,即使是从指向对象的指针调用时:

MyClass *obj = getObject();
obj->foo(); // As I understand it, this cannot be inlined. That's okay.
obj->wrapper(); // Nor will this. However, I hope that the machine code
                // for the wrapper function will contain inlined calls to
                // foo().

从本质上讲,我希望编译器生成包装函数的多个版本——每个可能的类一个——并内联调用适当的foo,这应该是可能的,因为在选择wrapper要执行的函数之前确定对象类型。这可能吗?有没有编译器支持这种优化?

编辑:我感谢到目前为止的所有反馈和答案,我最终可能会选择其中一个。但是,大多数回复忽略了我的问题的最后一部分,我解释了为什么我认为这种优化应该是可行的。这确实是我问题的症结所在,我仍然希望有人能解决这个问题。

编辑 2:我选择了 Vlad 的答案,因为他都建议了流行的解决方法并部分解决了我提出的优化(在 David 的答案的评论中)。感谢所有写答案的人——我都读了,没有一个明确的“赢家”。

此外,我发现一篇学术论文提出了与我的建议非常相似的优化:http ://www.ebb.org/bkuhn/articles/cpp-opt.pdf 。

4

5 回答 5

3

在某些情况下,编译器可以在编译时确定虚拟调度行为并执行非虚拟函数调用甚至内联函数。只有当它能够确定您的类是继承链中的“顶部”或者这两个函数没有以其他方式重载时,它才能做到这一点。通常,这根本不可能,特别是如果您没有为整个程序启用后期优化。

除非您想检查编译器优化的结果,否则最好的选择是根本不在内部循环中使用虚函数。例如,像这样:

class Foo {
  public:
    virtual void foo()
    {
        foo_impl();
    }

    virtual void bar()
    {
        for (int i = 0; i < ∞; ++i) {
            foo_impl();
        }
    }

  private:
    void foo_impl() { /* do some nasty stuff here */ }
};

但是在那种情况下,您显然放弃了有人可能会进来,从您的类继承并抛出他们自己的“foo”实现以由您的“bar”调用的想法。他们基本上将需要重新实现两者。

另一方面,它闻起来有点像过早的优化。现代 CPU 很可能会“锁定”您的循环,预测退出循环并一遍又一遍地执行相同的 µOP,即使您的方法实际上是虚拟的。所以我建议你在花时间优化它之前仔细确定这是一个瓶颈。

于 2012-10-18T21:11:34.440 回答
2

不,如果通过指针或引用(这包括指针)执行函数调用,则不会内联this。可以创建一个从当前类型扩展的新类型,并在不覆盖的foo情况下覆盖wrapper.

如果你想让编译器内联你的函数,你必须禁用该调用的虚拟调度:

void Type::wrapper() {
    Type::foo();            // no dynamic dispatch
}
于 2012-10-18T21:16:40.460 回答
2

想象以下层次结构:

class Base
{
    virtual void foo();
    virtual void wrapper();
};

class Derived1: public Base
{
    virtual void foo() { cout << "Derived1::foo"; }
    virtual void wrapper() { foo(); }
};

class Derived2: public Derived1
{
    virtual void foo() { cout << "Derived2::foo"; }
};

Base * p1 = new Derived1;
p1->wrapper();  // calls Derived1::wrapper which calls Derived1::foo
Base * p2 = new Derived2;
p2->wrapper();  // calls Derived1::wrapper which calls Derived2::foo

你能看出问题吗?Derived1::wrapper必须调用 Derived2::foo。直到运行时它才能知道它是调用 Derived1::foo 还是 Derived2::foo,所以没有办法内联它。

如果要确保内联是可能的,请确保要内联的函数不是虚拟的。从您的描述看来,如果层次结构中的每个类都重新实现fooand ,这可能是可能的wrapper。一个函数不需要是虚拟的就可以被覆盖。

于 2012-10-18T21:19:58.980 回答
1

这是不准确的。virtual函数可以内联,但前提是编译器确定地知道对象的静态类型 - 从而保证多态性有效。

例如:

struct A
{
    virtual void foo()
    {
    }
};
struct B
{
    virtual void foo()
    {
    }
};

int main()
{
    A a;
    a.foo(); //this can be inlined
    A* pa = new A;
    pa->foo(); //so can this
}

void goo(A* pa)
{
    pa->foo() //this probably can't
}

也就是说,在你的情况下,这似乎不可能发生。您可以做的是拥有另一个virtual实际实现该功能并静态调用它的非函数,因此该调用在编译时得到解决:

class MyClass
{
    virtual void foo() = 0;
    virtual void wrapper() = 0;
};

class Derived : MyClass
{
    void fooImpl()
    {
       //keep the actual implementation here
    }
    virtual void foo()
    {
       fooImpl();
    }
    virtual void wrapper()
    {
       for ( int i = 0 ; i < manyTimes ; i++ )
          fooImpl(); //this can get inlined
    }
};

或者Derived::foo()就像@avakar 指出的那样。

于 2012-10-18T21:08:52.013 回答
0

您的函数是virtual并且它的类型直到运行时才指示,那么您如何期望编译器将某个类的代码内联到其中?

在一些类似的情况下,我有 2 个函数:foo那是虚拟的,那是正常的函数,foo_impl将被调用foowrapper

于 2012-10-18T21:09:53.913 回答