[...]编译器能够消除额外的初始化吗?
几乎在所有情况下都可以。
我应该总是通过调用移动赋值运算符来编写移动构造函数吗?
是的,除非您测量了它会导致次优性能的情况。
今天的优化器在优化代码方面做得非常出色。您的示例代码尤其易于优化。首先:移动构造函数在几乎所有情况下都将被内联。如果您通过移动赋值运算符实现它,那么该函数也将被内联。
让我们看看一些汇编代码!这里展示了具有手动和通过移动赋值操作符两种版本的移动构造函数的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
除了为右版本添加的分支外,代码完全相同。意思是:删除了重复的赋值。
为什么需要额外的分支?如微软页面所定义的移动赋值运算符比移动构造函数做更多的工作:它可以防止自我赋值。移动构造函数没有这个保护。但是:正如我已经说过的,构造函数在几乎所有情况下都会被内联。在这些情况下,优化器可以看到它不是自我赋值,因此这个分支也将被优化掉。
这一点被反复强调,但很重要:不要进行过早的微观优化!
别误会我,我也讨厌由于懒惰或草率的开发人员或管理决策而浪费大量资源的软件。节约能源不仅关乎电池,也是一个环境问题,我非常热衷于此。但是,过早地进行微观优化并不有助于这方面!当然,将大型数据的算法复杂度和缓存友好性记在脑后。但在进行任何特定的优化之前,请先进行度量!
在这种情况下,我甚至猜想您永远不必手动优化,因为编译器总是能够在移动构造函数周围生成最佳代码。现在进行无用的微观优化将会在以后需要更改两个位置的代码或需要调试仅因更改一个位置的代码而发生的奇怪错误时浪费您的开发时间。这是可以用于做有用的优化的开发时间浪费。