虚拟函数可以内联

时间:2013-08-25 18:04:01

标签: c++ class inline virtual-destructor

如果我定义这样的类:

class A{
public:
    A(){}
    virtual ~A(){}
    virtual void func(){}
};

是否意味着虚拟析构函数和func内联

2 个答案:

答案 0 :(得分:11)

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

A* a = new B;
a->func();

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

正如hvd正确指出的那样,虚拟调度可以通过调用虚拟函数来完全限定,例如a->A::func(),在这种情况下也可以内联虚拟函数。通常不内联虚函数的主要原因是需要进行虚拟调度。但是,通过完全限定,要调用的函数是已知的。

答案 1 :(得分:4)

是的,并且有多种方式。您可以看到我在2年前发送到Clang邮件列表的 devirtualization in this email的一些示例。

与所有优化一样,这正在等待编译器消除备选方案的能力:如果它可以证明虚拟调用始终在Derived::func中解析,那么它可以直接调用它。

有各种各样的情况,让我们先从语义证据开始:

  • SomeDerived& d其中SomeDerived final允许所有方法调用的虚拟化
  • SomeDerived& dd.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也不会对其进行优化。问题是:

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

我跟踪了Clang和LLVM邮件列表上的工作,因为两组开发人员都在推断信息丢失以及如何让Clang告诉LLVM:“没关系”但不幸的是这个问题并不重要而且没有已经解决了......因此前端的半假性虚拟化试图得到所有明显的情况,有些不那么明显(尽管按照惯例,前端不是你实现它们的地方)。


作为参考,Clang中的虚拟化代码可以在名为canDevirtualizeMemberFunctionCalls的函数中的CGExprCXX.cpp中找到。它只有大约64行(现在),并且完全评论过。