我有一个包含两个虚拟成员函数的类:foo
和wrapper
。 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 :我选择了弗拉德的回答,因为他们都提出了流行的解决方法并部分解决了我提出的优化问题(在David的回答评论中)。感谢所有写过答案的人 - 我全部阅读,并没有明确的“赢家”。
另外,我发现一篇学术论文提出的优化与我的建议非常相似:http://www.ebb.org/bkuhn/articles/cpp-opt.pdf。
答案 0 :(得分: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”实施来被你的“酒吧”调用。他们基本上需要重新实施两者。
另一方面,它有点像过早的优化。现代CPU很可能会“锁定”您的循环,预测它的退出并反复执行相同的μOP,即使您的方法实际上是虚拟的。因此,我建议您在花时间优化之前仔细确定这是一个瓶颈。
答案 1 :(得分:2)
不,如果通过指针或引用(包括this
指针)执行,则不会内联函数调用。可以创建一个新类型,该类型从当前类型扩展并覆盖foo
而不覆盖wrapper
。
如果要启用编译器内联函数,则必须禁用该调用的虚拟调度:
void Type::wrapper() {
Type::foo(); // no dynamic dispatch
}
答案 2 :(得分: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,所以没有办法内联它。
如果要确保可以内联,请确保要内联的功能不是虚拟的。从您的描述中可以看出,如果层次结构中的每个类都重新实现了foo
和wrapper
,那么这可能是可能的。一个函数不需要被虚拟覆盖。
答案 3 :(得分: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
}
};
或仅仅{@ 1}} @avakar指出。
答案 4 :(得分:0)
你的函数是virtual
,它的类型在运行时才会显示,那么你希望编译器如何将某些类的代码内联到其中呢?
在某些类似的情况下,我有两个功能:foo
是虚拟的,foo_impl
是正常功能,将从foo
和wrapper