为了更直接地了解您可以期待什么,让我们从这样一个简单的类开始:
class Integer {
int a;
public:
Integer(int a) : a(a) {}
friend Integer operator+(Integer a, Integer b) {
return Integer(a.a + b.a);
}
friend std::ostream &operator<<(std::ostream &os, Integer const &i) {
return os << i.a;
}
};
为了演示,让我们添加一个main,它从外界读取一些数据,创建几个 Integer 对象,然后打印出添加它们的结果。输入和输出将来自外部世界,因此编译器不能太花哨地把所有东西都优化出来。
int main(int argc, char **argv) {
Integer a(atoi(argv[1])), b(atoi(argv[2]));
Integer c = a + b; // Line 20
std::cout << c;
}
注意那里的line 20——它在下面变得很重要。
现在,让我们编译它,看看编译器会生成什么代码。使用 VC++ 我们得到这个:
[设置main的条目的普通“东西”已省略]
; Line 19
mov rcx, QWORD PTR [rdx+8]
mov rdi, rdx
call atoi
mov rcx, QWORD PTR [rdi+16]
mov ebx, eax
call atoi
; Line 20
lea edx, DWORD PTR [rax+rbx]
; Line 21
call ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@H@Z ; std::basic_ostream<char,std::char_traits<char> >::operator<<
因此,即使我们创建了两个 Integer 对象并使用构造并返回第三个 Integer 对象的重载运算符添加它们,编译器已经看穿了我们所有的诡计,并“意识到”我们所做的只是使用atoi读取几个ints,将它们加在一起,然后打印出我们得到的int。
看到这一点,它完全消除了函数调用,不调用或返回任何东西——它只是读取两个整数,将它们相加,然后打印出结果。
使用 gcc 的结果几乎相同:
movq 8(%rbx), %rcx
call atoi ; <--- get first item
movq 16(%rbx), %rcx
movl %eax, %esi
call atoi ; <--- get second item
movq .refptr._ZSt4cout(%rip), %rcx
leal (%rsi,%rax), %edx ; <--- the addition
call _ZNSolsEi ; <--- print result
它稍微重新排列了代码,但ultimate 做了几乎相同的事情——我们Integer 类的所有痕迹都消失了。
让我们将其与完全不使用类的情况进行比较:
int main(int argc, char **argv) {
int a = atoi(argv[1]);
int b = atoi(argv[2]);
int c = a + b;
std::cout << c;
}
使用 VC++,这会产生以下结果:
; Line 5
mov rcx, QWORD PTR [rdx+8]
mov rdi, rdx
call atoi
; Line 6
mov rcx, QWORD PTR [rdi+16]
mov ebx, eax
call atoi
; Line 7
lea edx, DWORD PTR [rbx+rax]
; Line 8
call ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@H@Z ; std::basic_ostream<char,std::char_traits<char> >::operator<<
除了显示原始文件中行号的 cmets 之外,代码完全与我们使用该类得到的相同。
我不会浪费空间来复制和粘贴 g++ 的结果;无论我们是否使用我们的类和重载运算符进行加法,它也会产生相同的代码。