可以内联虚函数[重复]
Posted
技术标签:
【中文标题】可以内联虚函数[重复]【英文标题】:Can virtual functions be inlined [duplicate] 【发布时间】:2013-08-28 05:30:12 【问题描述】:如果我这样定义一个类:
class A
public:
A()
virtual ~A()
virtual void func()
;
是不是说虚析构函数和func
被内联了
【问题讨论】:
如果你仔细想想,内联虚函数并没有真正的意义。我能看到的唯一情况是您是否在编译时知道类型,但即便如此我也不确定编译器是否会进行优化。 ***.com/questions/733737/… @Borgleader:他们会在可能的时候这样做。然而,没有编译器真正擅长它,因为 C++ 语言中关于多态对象的构造和销毁的复杂规则。此外,由于一般情况下没有 JITing,因此可以完成的情况非常有限。 【参考方案1】:编译器是否选择内联定义为内联的函数完全取决于编译器。一般来说,virtual
函数只有在编译器可以证明静态类型与动态类型匹配或编译器可以安全地确定动态类型时才能内联。例如,当您使用 A
类型的值时,编译器知道动态类型不能不同,它可以内联函数。当使用指针或引用时,编译器通常无法证明静态类型相同,virtual
函数通常需要遵循通常的虚拟调度。但是,即使使用了指针,编译器也可能从上下文中获得足够的信息来了解确切的动态类型。例如,马蒂厄姆。举了以下例子:
A* a = new B;
a->func();
在这种情况下,编译器可以确定a
指向B
对象,因此无需动态分派即可调用func()
的正确版本。无需动态调度,func()
就可以被内联。当然,编译器是否做相应的分析取决于其各自的实现。
正如 hvd 正确指出的那样,可以通过调用一个完全限定的虚函数来规避虚拟调度,例如,a->A::func()
,在这种情况下,也可以内联虚函数。虚拟函数通常不内联的主要原因是需要进行虚拟调度。但是,在完全限定条件下,要调用的函数是已知的。
【讨论】:
对虚函数的非虚调用 (a->A::func()
) 是另一个比较明显的例子,其中内联通常有效。
我查看了@Mat 给出的链接,似乎内联的虚拟析构函数是有道理的,但我仍然对析构函数的内联方式有点困惑
当编译器可以证明静态类型与动态类型匹配时:实际上比这更复杂。考虑Base* b = new Derived; b->func();
,如果编译器足够聪明地意识到b
的动态类型必然是Derived
,则可以内联调用。 Clang 是一个非常聪明的编译器。
@Ghostblade:这更像是一个追踪价值来源的游戏,看看它是否可以解析成一个具体的类型:) 不过,做那种追踪知道似乎很可惜LLVM 也会这样做:/
@Ghostblade:不。我的意思是,在某些条件下,也可以在编译时确定要调用的正确析构函数。在这个意义上,析构函数并不特殊。当在编译时确定正确的析构函数时,它可以是内联的。另一部分只是描述了析构函数的虚拟调度有什么特别之处,但它对析构函数是否可以内联没有任何影响。【参考方案2】:
是的,而且有多种方式。你可以看到一些去虚拟化的例子in this email我大约2年前发送到Clang邮件列表。
与所有优化一样,这取决于编译器消除替代方案的能力:如果它可以证明虚拟调用总是在 Derived::func
中解决,那么它可以直接调用它。
情况多种多样,我们先从语义证据开始:
SomeDerived& d
其中SomeDerived
是final
允许对所有方法调用进行去虚拟化
SomeDerived& d
, d.foo()
其中foo
是final
也允许对这个特定调用进行去虚拟化
那么,有些情况你知道对象的动态类型:
SomeDerived d;
=> d
的动态类型必然是SomeDerived
SomeDerived d; Base& b;
=> b
的动态类型必然是SomeDerived
这 4 种去虚拟化情况通常由编译器前端解决,因为它们需要有关语言语义的基础知识。我可以证明所有 4 个都是在 Clang 中实现的,我认为它们也在 gcc 中实现。
但是,在很多情况下都会出现这种情况:
struct Base virtual void foo() = 0; ;
struct Derived: Base virtual void foo() std::cout << "Hello, World!\n"; ;
void opaque(Base& b);
void print(Base& b) b.foo();
int main()
Derived d;
opaque(d);
print(d);
尽管这里很明显对foo
的调用被解析为Derived::foo
,但Clang/LLVM 不会对其进行优化。问题是:
d.foo()
替换print(d)
和去虚拟化调用
LLVM(后端)不知道语言的语义,因此即使在将print(d)
替换为d.foo()
之后,它仍假定d
的虚拟指针可能已被opaque
(其定义顾名思义,是不透明的)
我一直在关注 Clang 和 LLVM 邮件列表上的努力,因为两组开发人员都在推理信息丢失以及如何让 Clang 告诉 LLVM:“没关系”,但不幸的是,这个问题并不重要,而且还没有还没有解决……因此,前端半途而废的去虚拟化试图获取所有明显的案例,还有一些不那么明显的案例(尽管按照惯例,前端不是你实现它们的地方)。
作为参考,Clang 中的去虚拟化代码可以在 CGExprCXX.cpp 的一个名为 canDevirtualizeMemberFunctionCalls
的函数中找到。它只有大约 64 行长(现在)并且经过了彻底的评论。
【讨论】:
+1 参考代码。以上是关于可以内联虚函数[重复]的主要内容,如果未能解决你的问题,请参考以下文章
缺少 vtable 通常意味着第一个非内联虚成员函数没有定义
C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?