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