3

有人可以解释下面的段落,没有给出解释:

它起作用的原因是,在 C++ 的几乎所有实现中,虚函数都是用“虚函数表”实现的,它是指向函数的指针数组。这与函数内联不兼容,因为您根本无法拥有指向内联代码的指针。因此在大多数情况下,virtual 和 inline 是不兼容的;编译器将简单地忽略内联语句。但是,在一种情况下它们是兼容的:直接访问对象而不是通过指针访问对象

“C++ 中的高性能游戏编程”,Paul Pedriana

有人可以解释为什么粗体字的两个句子是这样的吗?

4

3 回答 3

5

它只是意味着编译器不会内联它必须在运行时解析的调用。

假设你有

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

void test(A* a)
{ 
   a->foo();
}

此调用在运行时解决。由于foo()可能已被派生类覆盖并且a可能指向派生类型的对象,a->foo()因此不能内联 - 它将通过查找来解决。

第二条语句意味着在某些情况下可以内联函数virtual

void test(A a)
{
   a.foo();
}

在这种情况下,ais 类型A(即使传递给函数的原始对象是派生类型,它也被切片,因为您按值传递) - 调用保证调用A::foo,因此它可以被编译器内联。

于 2013-06-29T13:17:37.673 回答
4

首先,关键字在任何方面都不是不兼容的。该段落正在讨论底层实现。一个特定的函数调用可能是内联的,也可能是通过虚拟分派的,但通常不是两者兼而有之。要了解原因,您只需要了解什么是内联调用或虚拟调用。

内联函数调用是至少部分被调用函数直接拼接到调用函数中的调用。这是一种低级优化,编译器甚至可以对未定义为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函数,并且可以在头文件中定义函数。这就是最重要的。

于 2013-06-29T13:35:06.007 回答
0

它不是“不兼容的”,只是在实际放置虚拟调用时,不可能内联未知代码。当对函数进行静态调用时,内联将正常工作。

文本的格式不是很好,但如果你已经知道它想表达什么,你可以在那里找到它。;-)

于 2013-06-29T13:19:18.880 回答