内联虚函数真的是无意义的吗?
当我收到一个代码评论评论,说虚拟function不需要内联时,我得到了这个问题。
我认为内联虚函数可以在直接调用对象的场景中派上用场。 但是我想到的反驳是 – 为什么要定义虚拟然后使用对象来调用方法?
最好不要使用内联虚函数,因为它们几乎从来没有扩展?
我用于分析的代码片段:
class Temp { public: virtual ~Temp() { } virtual void myVirtualFunction() const { cout<<"Temp::myVirtualFunction"<<endl; } }; class TempDerived : public Temp { public: void myVirtualFunction() const { cout<<"TempDerived::myVirtualFunction"<<endl; } }; int main(void) { TempDerived aDerivedObj; //Compiler thinks it's safe to expand the virtual functions aDerivedObj.myVirtualFunction(); //type of object Temp points to is always known; //does compiler still expand virtual functions? //I doubt compiler would be this much intelligent! Temp* pTemp = &aDerivedObj; pTemp->myVirtualFunction(); return 0; }
虚拟function有时可以内联。 摘自优秀的C ++常见问题解答 :
“只有当编译器知道作为虚拟函数调用目标的对象的”精确类“时,内联虚拟调用才能被内联,只有当编译器具有实际对象而不是指针或引用一个对象,也就是说,要么是一个局部对象,一个全局/静态对象,要么是一个完全包含的对象。
C ++ 11已经添加了final
。 这改变了接受的答案:不再需要知道对象的确切类别,知道该对象至less具有声明该函数最终的类types就足够了:
class A { virtual void foo(); }; class B : public A { inline virtual void foo() final { } }; class C : public B { }; void bar(B const& b) { A const& a = b; // Allowed, every B is an A. a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C. }
有一类虚拟函数在内联中仍然有意义。 考虑以下情况:
class Base { public: inline virtual ~Base () { } }; class Derived1 : public Base { inline virtual ~Derived1 () { } // Implicitly calls Base::~Base (); }; class Derived2 : public Derived1 { inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 (); }; void foo (Base * base) { delete base; // Virtual call }
删除“base”的调用将执行一个虚拟调用来调用正确的派生类析构函数,这个调用不是内联的。 但是因为每个析构函数都调用它的父析构函数(在这种情况下是空的),所以编译器可以内联这些调用,因为它们不会虚拟地调用基类函数。
对于基类构造函数或派生实现也调用基类实现的任何函数集,都存在相同的原则。
我见过没有任何非内联函数存在的情况下不会发出任何v-表的编译器(并且在一个实现文件中定义,而不是在头中定义)。 他们会抛出像missing vtable-for-class-A
或类似的东西的错误,你会像我一样地迷糊糊糊。
事实上,这不符合标准,但是它发生了,所以考虑至less把一个虚函数放在头文件中(如果只是虚析构函数),以便编译器可以在这个地方为类发出一个vtable。 我知道它发生在一些版本的gcc
。
正如有人提到的,内联虚函数有时候可以是一种好处,但是当你不知道对象的dynamictypes时,通常会使用内联虚函数,因为这是virtual
的首要原因。
但是编译器不能完全忽略inline
。 除了加速函数调用之外,它还有其他的语义。 类内定义的隐式内联是允许您将定义放入标题的机制:只有inline
函数可以在整个程序中定义多次,而不会违反任何规则。 最后,即使您将标题多次包含在链接在一起的不同文件中,它的行为也会像在整个程序中只定义一次一样。
那么,实际上虚拟函数总是可以内联 ,只要它们静态地连接在一起:假设我们有一个抽象类Base
带有一个虚函数F
和派生类Derived1
和Derived2
:
class Base { virtual void F() = 0; }; class Derived1 : public Base { virtual void F(); }; class Derived2 : public Base { virtual void F(); };
一个认真的调用b->F();
(带有Base*
types的b
)显然是虚拟的。 但是你(或者编译器 …)可以像这样重写它(假设typeof
是一个typeid
类函数,它返回一个可以在switch
使用的值)
switch (typeof(b)) { case Derived1: b->Derived1::F(); break; // static, inlineable call case Derived2: b->Derived2::F(); break; // static, inlineable call case Base: assert(!"pure virtual function call!"); default: b->F(); break; // virtual call (dyn-loaded code) }
虽然我们仍然需要RTTI来处理typeof
,但调用可以通过基本上将vtableembedded到指令stream内部并专门调用所有相关的类来有效地进行内联。 这也可以通过专门只有几个类(也就是说Derived1
)来Derived1
:
switch (typeof(b)) { case Derived1: b->Derived1::F(); break; // hot path default: b->F(); break; // default virtual call, cold path }
内联真的不能做任何事情 – 这是一个暗示。 编译器可能忽略它,或者如果它看到实现并且喜欢这个想法,它可能内联一个没有内联的调用事件。 如果代码清晰度受到威胁,应该删除内联 。
内联声明虚函数在通过对象调用时内联,在通过指针或引用调用时被忽略。
在线标记虚拟方法有助于在以下两种情况下进一步优化虚拟function:
使用现代编译器,不会损害它们。 一些古老的编译器/链接器组合可能已经创build了多个vtable,但我不认为这是一个问题了。
在函数调用是明确的并且函数是内联的合适候选者的情况下,编译器足够聪明以便内联代码。
其余的时间“内联虚拟”是无稽之谈,事实上一些编译器不会编译该代码。
编译器只能在编译时明确parsing调用的情况下内联函数。
虚拟函数在运行时被parsing,所以编译器不能内联调用,因为在编译types中,dynamictypes(因此被调用的函数实现)不能被确定。
做虚拟函数,然后在对象而不是引用或指针上调用它们是有意义的。 Scott Meyer在其“有效的c ++”一书中build议不要重新定义一个inheritance的非虚函数。 这是有道理的,因为当你用非虚函数创build一个类并在派生类中重新定义函数时,你可能一定要正确地使用它,但是你不能确定别人会正确地使用它。 此外,您可能会在以后使用它不正确yoruself。 所以,如果你在一个基类中做了一个函数,并且你希望它可以被重定义,那么你应该把它变成虚拟的。 如果把虚拟函数和对象称为虚拟函数是有意义的,那么内联它们也是有意义的。