whover downvoted me,介意解释原因?我认为这是一个合理的问题,所有答案都非常有用。
理论上,当我MyClass a = b + c
时,它应该首先调用const MyClass operator+
,返回一个const MyClass
对象,然后调用赋值运算符来创建对象a。
似乎我会在返回一个对象并调用赋值运算符时复制两次。这是在编译器中优化的吗?如果有,怎么样?如果它涉及铸造似乎更棘手。
让我们假设我们正在谈论g ++,这几乎是c ++编译器的黄金标准。 [编辑:好的,让我们说最常用的]
[编辑:]哇,我没有期望使用const按值返回会受到批评。我认为在非内置类型的返回值时使用const是值得的吗?我记得在某个地方看过它。
答案 0 :(得分:2)
复制初始化不使用赋值运算符,它使用复制或移动构造函数。由于您的运算符愚蠢地返回const
对象,因此无法移动,因此它将使用复制构造函数。
但是,从临时初始化对象是允许 copy elision 的情况之一,因此任何体面的编译器都应该这样做,直接初始化a
作为返回值而不是创造一个临时的。
答案 1 :(得分:1)
大多数编译器都会使用 copy-elision 对其进行优化。从调用MyClass::operator+
创建的临时文件将直接构造到a
而不是调用复制构造函数。
另请注意,MyClass a = ...
不会调用赋值运算符,而是调用复制构造函数。这称为复制初始化。
有关复制省略的更多信息,请查看here。
答案 2 :(得分:0)
有一项名为copy elision的优化,由§12.8/ 31中的标准描述:
复制/移动操作的省略,称为 copy elision ,是 在下列情况下允许(可以合并到 消除多份副本):
在具有类返回类型的函数中的
return
语句中,当表达式是a的名称时 非易失性自动对象(函数或catch子句参数除外)具有相同的cv- 非限定类型作为函数返回类型,可以通过构造省略复制/移动操作 自动对象直接进入函数的返回值当一个尚未绑定到引用(12.2)的临时类对象被复制/移动到具有相同的类对象时 cv-unqualified type,可以省略复制/移动操作 将临时对象直接构造到目标中 省略复制/移动
因此在行
MyClass a = b + c;
operator+
返回的临时对象直接构建到a
,并且不会发生不必要的副本/移动,即使在return
语句中也没有{1}}。演示:
operator+
任何体面编译器的输出:
Ctor!
Ctor!
Ctor!
* Dtor!
* Dtor!
* Dtor!
答案 3 :(得分:0)
为了更直接地了解您的期望,让我们从这样一个简单的类开始:
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 ++我们得到这个:
[普通&#34;东西&#34;设置main
elided]的条目
; 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对象的重载运算符添加它们,编译器已经看透了我们所有的子程序,并且&#34;实现了&#34;我们正在做的只是使用int
阅读几个atoi
,将它们添加到一起,然后打印出我们得到的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
它稍微重新排列了代码,但是终极几乎完全相同 - 我们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<<
除了显示原始文件中行号的注释外,代码与我们使用该类的内容完全相同。
我不会浪费空间来复制和粘贴g ++的结果;它也产生相同的代码,无论我们是否使用我们的类和重载运算符来进行添加。