有人可以解释下面的段落,没有给出解释:
它起作用的原因是,在 C++ 的几乎所有实现中,虚函数都是用“虚函数表”实现的,它是指向函数的指针数组。这与函数内联不兼容,因为您根本无法拥有指向内联代码的指针。因此在大多数情况下,virtual 和 inline 是不兼容的;编译器将简单地忽略内联语句。但是,在一种情况下它们是兼容的:直接访问对象而不是通过指针访问对象。
“C++ 中的高性能游戏编程”,Paul Pedriana
有人可以解释为什么粗体字的两个句子是这样的吗?
它只是意味着编译器不会内联它必须在运行时解析的调用。
假设你有
struct A
{
virtual void foo() {};
};
和
void test(A* a)
{
a->foo();
}
此调用在运行时解决。由于foo()
可能已被派生类覆盖并且a
可能指向派生类型的对象,a->foo()
因此不能内联 - 它将通过查找来解决。
第二条语句意味着在某些情况下可以内联函数virtual
:
void test(A a)
{
a.foo();
}
在这种情况下,a
is 类型A
(即使传递给函数的原始对象是派生类型,它也被切片,因为您按值传递) - 调用保证调用A::foo
,因此它可以被编译器内联。
首先,关键字在任何方面都不是不兼容的。该段落正在讨论底层实现。一个特定的函数调用可能是内联的,也可能是通过虚拟分派的,但通常不是两者兼而有之。要了解原因,您只需要了解什么是内联调用或虚拟调用。
内联函数调用是至少部分被调用函数直接拼接到调用函数中的调用。这是一种低级优化,编译器甚至可以对未定义为inline
. 但是在早期编译器比较笨且可执行代码大小更重要的时候,关键字与优化功能更直接相关。
虚拟调用是调用者不确切知道将执行什么函数的调用。需要在运行时根据类查找(调度)函数的实现。
所以如果函数只会在运行时确定,编译器就无法将两个函数拼接在一起。它不知道会拼接什么功能。
但是virtual
您可以在不进行特殊调度的情况下调用函数。如果编译器可以在编译时确定对象的实际类型,就会发生这种情况。例如,如果dog
是从pet
一个virtual
方法派生的speak
,
dog fido;
fido.speak(); // direct dispatch: we know fido is a dog.
但如果我们有参考,情况并非如此:
bird buddy;
pet &favorite = prefer_dogs? fido : buddy;
favorite.speak(); // favorite may be a bird or dog: need virtual dispatch
大多数时候,当你调用一个virtual
函数时,你是通过虚拟调度来实现的。
还有另一种情况,据我所知,这只是理论上的:如果编译器可以确定整个类层次结构(或给定实例中的选择),它可以添加一个布尔测试是否favorite
是 abird
或 adog
和内联两者函数调用作为自动生成if … else
语句中的替代方案。但无论如何,这只是你不应该担心的具体细节。
重要的是根据virtual
对象定义的类型调用inline
函数,并且可以在头文件中定义函数。这就是最重要的。
它不是“不兼容的”,只是在实际放置虚拟调用时,不可能内联未知代码。当对函数进行静态调用时,内联将正常工作。
文本的格式不是很好,但如果你已经知道它想表达什么,你可以在那里找到它。;-)