在构造函数是另一个编译单元的实际代码中,编译器将无法看到构造函数的主体(因此有 noinline 属性)。模仿一些现实世界的需求也不是最终的。
为了去虚拟化,编译器通常需要能够证明类层次结构是密封的。如果对构造函数的调用在单独的翻译单元中,编译器无法证明这一点。但是,使用link-time optimization 可以为优化器提供跨翻译单元的信息,从而更容易证明有关类层次结构和引用的事实。
这是一个使用 clang 的示例。
b.hpp
#ifndef B_H
#define B_H
struct B {
virtual int foo();
};
#endif
b.cpp
#include "b.h"
int B::foo() { return 3; };
c.hpp
#ifndef C_H
#define C_H
#include "b.h"
struct C {
B& b;
C(B& b);
int foo();
};
#endif
c.cpp
#include "c.h"
C::C(B& b) : b(b) {}
int C::foo() {
return b.foo();
}
main.cpp
#include <iostream>
#include "b.h"
#include "c.h"
int main(const int argc, const char* argv[argc]) {
B b;
C c(b);
std::cout << c.foo() << std::endl;
return 0;
}
由于优化器对C::C 的调用站点一无所知(
构造函数)它对B 的运行时类型一无所知。所以,不能
去虚拟化B::foo。
C::foo
_ZN1C3fooEv: # @_ZN1C3fooEv
.cfi_startproc
# BB#0:
movq (%rdi), %rdi
movq (%rdi), %rax
jmpq *(%rax) # TAILCALL <== pointer call
但是,给优化器链接时间
信息 (-flto) 允许它证明类层次结构与调用站点隔离。
B::foo
0000000000400960 <_ZN1B3fooEv>:
400960: b8 03 00 00 00 mov $0x3,%eax
400965: c3 retq
400966: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40096d: 00 00 00
主要
0000000000400970 <main>:
400970: 41 56 push %r14
400972: 53 push %rbx
400973: 50 push %rax
400974: 48 c7 04 24 78 0a 40 movq $0x400a78,(%rsp)
40097b: 00
40097c: 48 8d 3c 24 lea (%rsp),%rdi
400980: e8 db ff ff ff callq 400960 <_ZN1B3fooEv> # <== direct call