内联虚函数

26
在C++中,我的理解是虚函数可以被内联,但通常会忽略内联提示。似乎内联的虚函数并没有太大意义。
是这样吗?
有人能给出一个内联虚函数有用的案例吗?

我在这里没有看到一个真正的问题... - Mitch Wheat
1
嗨,你是什么意思?我没有清楚地解决问题吗?对此我感到很抱歉。 - skydoor
4
内联函数从未是必需的,因此很难向您展示必要的内联虚拟函数。 - zneak
给zneak:哇...这是真的。非常感谢。 - skydoor
1
可能是内联虚函数真的毫无意义吗?的重复问题。 - Theodore Murdock
3个回答

46
为了完整回答这个问题,需要了解到virtual属性既可以应用于函数本身,也可以应用于调用该函数的方法。有虚拟和非虚拟函数,还有对这些函数的虚拟和非虚拟调用。
同样适用于inline属性。存在内联和非内联函数,以及对这些函数的内联非内联的调用。
这些属性-virtualinline-当应用于函数本身时不会冲突。它们只是没有理由和机会发生冲突。对于函数本身,inline关键字所做的唯一修改就是修改该函数的一个定义规则:该函数可以在多个翻译单元中被定义(并且必须在使用它的每个翻译单元中都被定义)。而virtual关键字所做的唯一修改就是使包含该函数的类成为多态。它对函数本身没有实际影响。
因此,在声明一个函数同时使用virtualinline关键字时,绝对没有问题。基础原则上不存在任何冲突。在C++语言中这是完全合法的。
struct S {
  virtual void foo(); 
};

inline void S::foo() // virtual inline function - OK, whatever
{
}

然而,当人们提出这个问题时,通常不是对函数本身的属性感兴趣,而是对调用该函数的特征感兴趣。

虚函数调用的定义特征是它在运行时被解析,这意味着真正的虚函数调用通常无法进行内联:

S *s = new SomeType;
s->foo(); // virtual call, in general case cannot be inlined

然而,如果调用本身不是虚函数(即使它调用了一个虚函数),那么内联就根本不是问题:

S *s = new SomeType;
s->S::foo(); // non-virtual call to a virtual function, can easily be inlined

当然,在某些情况下,优化编译器可能能够在编译时找出虚函数调用的目标,并内联甚至是这样的虚函数调用。在某些情况下很容易:

S ss;
ss.foo(); // formally a virtual call, but in practice it can easily be inlined

在某些情况下,这可能更加复杂,但仍然可行:

S *s = new S;
s->foo(); // virtual call, but a clever compiler might be able
          // to figure out that it can be inlined

你怎么能说 ss.foo(); 形式上是一个虚函数调用呢? - Belloc
@Belloc:因为在C++中,所有对虚函数的调用都是“虚调用”,即根据对象的动态类型解析。语言规范中没有任何说明ss.foo()有什么特殊或不同的地方。唯一忽略“动态类型”的调用是限定名称的调用。 - AnT stands with Russia
我不是gcc和clang的专家,但据我所知,-O0是两个编译器中最低的优化级别。顺便说一句,使用-O0开关在clang中可以得到相同的结果。无论如何,你能否给我一个标准的引用,证实你所说的内容? - Belloc
@Belloc:此外,仔细想想,我甚至不会称其为“优化”。真正的编译器优化是指编译器生成的代码与抽象C++机器的行为偏离(当然仍保留可观察行为)。在这种情况下,没有偏离。调用根据对象的动态类型解析,就像抽象的C++机器一样。调用是直接分派还是间接分派(即在反汇编中看到的内容)完全无关紧要。 - AnT stands with Russia
我现在必须离开。明天我会回来。非常感谢您关注并回答我的评论。 - Belloc
显示剩余4条评论

14

在正常情况下,虚函数将通过指向函数的指针(包含在类的虚函数表中)来调用。因此,只有在编译器可以静态确定将调用该函数的实际类型时,即它必须是类X或继承自X的某些内容,才能在内联方式下生成虚函数调用。

内联虚函数的主要应用场景是在性能至关重要的情况下,并且已知一个类将经常以一种方式使用,使得编译器可以静态地确定实际类型(并且至少有一个目标编译器可以通过指针优化掉调用)。


1
我也发现当短函数内联在类体中时,它们更清晰。 - Omnifarious
2
问题涉及关键字 inline - Georg Fritzsche
此外,我认为如果你的虚函数只是调用其他函数(这可能会发生在多重继承或其他原因),编译器可以将其thunking直接放入vtable中。 - Omnifarious
4
@ gf,不,它涉及内联虚函数的概念。在类体中声明的函数被隐式声明为内联函数,即使没有inline关键字也是如此。 - Omnifarious
说的也有道理,但这并不一定意味着“内联于类体中”。 - Georg Fritzsche

4
你可以将虚函数定义为内联函数。决定函数是否调用内联不仅在编译时确定,也可能在编译到运行时之间的任何时间确定。你可以参考Herb Sutter的这篇文章:Inline Redux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接