3
A * a = new B();
a->foo();

假设 B 派生 A 并且 foo() 是一个虚函数。这个例子很常见,问题是在某些地方据说编译器不会尝试内联它,而在其他地方则完全相反。

我个人看不出这个调用不能被内联的原因,因为在编译时很容易判断哪个函数被调用。

编辑1:我知道“一般情况”,也知道编译器需要很多因素来决定是否内联。如果我问编译器是否可以内联这个特定的调用,这个问题可能会更好。

我问这个的原因是 这个 C++ FAQ中的这个特殊引用,它说:

当通过指针或引用引用对象时,不能内联对虚函数的调用,因为调用必须动态解析。

4

4 回答 4

4

如果编译器可以以某种方式推断出所指向的对象a是一个实例B(即,如果代码是微不足道的,就像在您的示例中一样),那么它可以对调用进行虚拟化 - 尽管不需要这样做。

但关键是,一般来说,调用是在运行时解决的,因为你不知道(编译器也不知道!) 指向的对象的动态类型是什么a

换句话说,如果编译器不知道 的动态类型是什么*a,因此直到运行时才知道应该调用哪个foo()函数,那么就不可能在编译时对调用进行去虚拟化并内联。

于 2013-06-28T10:41:23.487 回答
2

澄清一下,g++ 显然至少正确地内联了一些微不足道的虚拟调用案例;这与您的情况类似,只是稍作调整以获得更具可读性的汇编程序:)

void test(int);

class A { public: virtual int Bop() { return 1; } };

class B : public A { virtual int Bop() { return 4711; } };

int main() {
  A* a = new B();
  test(a->Bop());
}

$ g++ -O99 -S -c test.cpp

$ less test.s
...
main:
.LFB2:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $8, %edi
    call    _Znwm
    movl    $4711, %edi            // hard coded 4711
    movq    $_ZTV1B+16, (%rax)
    call    _Z4testi               // calls test()
...
于 2013-06-28T11:02:44.540 回答
0

想象一下这样的翻译单元:

计算.cpp:

#include "A.h"

int compute(A * p)
{
    return p->get_some_int() * 10;
}

你认为虚函数调用如何可能被去虚拟化?这是一个使用示例:

主.cpp:

#include "B.h"
#include "C.h"

int compute(A *);

int main()
{
    A * p = rand() % 2 == 0 ? new B : new C;
    return compute(x);
}
于 2013-06-28T10:39:00.447 回答
0

你不能指望编译器内联它。这取决于编译器和选择的优化级别。鉴于 B::foo 的实现是可见的,并且您在分配和调用之间没有做任何事情,那么,是的,目视检查表明有足够的信息可供编译器进行优化。

但这不是必须的。

在很多其他情况下,它不会有足够的信息。

于 2013-06-28T11:00:43.583 回答