虚拟函数可以内联

Gho*_*ade 9 c++ inline class virtual-destructor

如果我定义这样的类:

class A{
public:
    A(){}
    virtual ~A(){}
    virtual void func(){}
};
Run Code Online (Sandbox Code Playgroud)

这是否意味着虚拟析构函数并且func内联

Die*_*ühl 11

编译器是否选择内联定义内联函数完全取决于编译器.通常,virtual只有当编译器可以证明静态类型与动态类型匹配或者编译器可以安全地确定动态类型时,才能内联函数.例如,当您使用类型A的值时,编译器知道动态类型不能不同,并且它可以内联函数.当使用指针或引用时,编译器通常不能证明静态类型是相同的,并且virtual函数通常需要遵循通常的虚拟分派.但是,即使使用指针,编译器也可能从上下文中获得足够的信息以了解确切的动态类型.例如,MatthieuM.给出了以下例外:

A* a = new B;
a->func();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,编译器可以确定a指向B对象的点,从而调用func()没有动态调度的正确版本.无需动态调度,func()就可以内联.当然,编译器是否进行相应的分析取决于其各自的实现.

由于HVD正确地指出,虚拟调度可以通过调用虚函数将全部限定,例如,被规避a->A::func(),在这种情况下,虚函数也可以内联.通常不内联虚函数的主要原因是需要进行虚拟调度.但是,通过完全限定,要调用的函数是已知的.

  • 对虚函数的非虚拟调用(`a-> A :: func()`)是内联通常有效的另一个明显的例子. (3认同)
  • *当编译器可以证明静态类型与动态类型*匹配时:它实际上比那更复杂.考虑`Base*b = new Derived {}; b-> func();`,如果编译器足够聪明,可以内联调用`b`的动态类型必然是`Derived`.Clang是一个如此聪明的编译器. (2认同)

Mat*_* M. 5

是的,并且以多种方式。您可以在我大约 2 年前发送到 Clang 邮件列表的这封电子邮件中看到一些去虚拟化的示例。

与所有优化一样,这取决于编译器消除替代方案的能力:如果它可以证明虚拟调用始终被解析,Derived::func那么它可以直接调用它。

情况有很多种,我们先从语义证据开始:

  • SomeDerived& d其中SomeDerived允许final对所有方法调用进行去虚拟化
  • SomeDerived& dd.foo()其中foofinal允许此特定调用的去虚拟化

然后,在某些情况下您知道对象的动态类型:

  • 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);
}
Run Code Online (Sandbox Code Playgroud)

尽管这里很明显对 的调用foo已解析为Derived::foo,但 Clang/LLVM 不会对其进行优化。问题在于:

  • Clang(前端)不执行内联,因此它无法替换print(d)d.foo()去虚拟化调用
  • LLVM(后端)不知道该语言的语义,因此即使在替换后,它print(d)d.foo()假设 的虚拟指针d可能已被更改opaque(其定义是不透明的,顾名思义)

我一直在关注 Clang 和 LLVM 邮件列表,因为两组开发人员都对信息丢失以及如何让 Clang 告诉 LLVM:“没关系”进行了推理,但不幸的是,这个问题并不简单,而且尚未得到解决...因此,前端的半途而废的去虚拟化尝试获取所有明显的情况,以及一些不那么明显的情况(尽管按照惯例,前端不是您实现它们的地方)。


作为参考,Clang 中的去虚拟化代码可以在CGExprCXX.cpp 的一个名为 的函数中找到canDevirtualizeMemberFunctionCalls。它只有约 64 行长(现在)并且有详尽的评论。