[...] 编译器是否能够优化掉额外的初始化?
在几乎所有情况下:是的。
我是否应该总是通过调用移动赋值运算符来编写移动构造函数?
是的,只需通过移动赋值运算符实现它,除非在您测量它导致次优性能的情况下。
今天的优化器在优化代码方面做得非常出色。您的示例代码特别容易优化。首先:移动构造函数在几乎所有情况下都会被内联。如果您通过移动赋值运算符实现它,则该运算符也将被内联。
让我们看看一些组装!这显示了来自 Microsoft 网站的确切代码,其中包含两个版本的移动构造函数:手动和通过移动赋值。以下是 GCC 的汇编输出-O
(-O1
具有相同的输出;clang 的输出导致相同的结论):
; ===== manual version ===== | ; ===== via move-assig =====
MemoryBlock(MemoryBlock&&): | MemoryBlock(MemoryBlock&&):
mov QWORD PTR [rdi], 0 | mov QWORD PTR [rdi], 0
mov QWORD PTR [rdi+8], 0 | mov QWORD PTR [rdi+8], 0
| cmp rdi, rsi
| je .L1
mov rax, QWORD PTR [rsi+8] | mov rax, QWORD PTR [rsi+8]
mov QWORD PTR [rdi+8], rax | mov QWORD PTR [rdi+8], rax
mov rax, QWORD PTR [rsi] | mov rax, QWORD PTR [rsi]
mov QWORD PTR [rdi], rax | mov QWORD PTR [rdi], rax
mov QWORD PTR [rsi+8], 0 | mov QWORD PTR [rsi+8], 0
mov QWORD PTR [rsi], 0 | mov QWORD PTR [rsi], 0
| .L1:
ret | rep ret
除了正确版本的附加分支外,代码完全相同。含义:重复的分配已被删除。
为什么要增加分支?Microsoft 页面定义的移动赋值运算符比移动构造函数做更多的工作:它可以防止自赋值。移动构造函数不受此保护。但是:正如我已经说过的,构造函数在几乎所有情况下都会被内联。在这些情况下,优化器可以看到它不是自赋值,所以这个分支也会被优化掉。
这得到了很多重复,但很重要:不要过早进行微优化!
不要误会我的意思,我也讨厌由于懒惰或马虎的开发人员或管理决策而浪费大量资源的软件。而节能不仅仅是电池,也是一个我非常热衷的环保话题。但是,过早地进行微优化在这方面没有帮助!当然,将大数据的算法复杂性和缓存友好性放在脑后。但在进行任何特定优化之前,请先测量!
在这种特定情况下,我什至猜想您永远不必手动优化,因为编译器将始终能够围绕您的移动构造函数生成最佳代码。现在进行无用的微优化将花费您以后的开发时间,因为您需要在两个地方更改代码,或者当您需要调试一个奇怪的错误时,该错误只会因为您只在一个地方更改代码而发生。那是浪费的开发时间,本来可以花在做有用的优化上。