对于 libc++,字符串移动构造函数确实清空了源代码,但这并不是必需的。事实上,这个字符串实现的作者就是领导 C++11 移动语义提案的同一个人。 ;-)
libc++字符串的这种实现实际上是从向外移动成员的角度设计的!
这是省略了一些不必要的细节(例如调试模式)代码的代码:
template <class _CharT, class _Traits, class _Allocator>
basic_string<_CharT, _Traits, _Allocator>::basic_string(basic_string&& __str)
_NOEXCEPT
: __r_(_VSTD::move(__str.__r_))
{
__str.__zero();
}
简而言之,此代码复制源的所有字节,然后将源的所有字节归零。需要立即注意的一件事:没有分支:此代码对长字符串和短字符串执行相同的操作。
长串模式
在“长模式”下,布局为 3 个字、一个数据指针和两个用于存储大小和容量的整数类型,减去 1 位用于长/短标志。加上分配器的空间(针对空分配器进行了优化)。
因此,这会复制指针/大小,然后清空源以释放指针的所有权。这还将源设置为“短模式”,因为短/长位意味着零状态下的短。此外,短模式中的所有零位都表示零大小、非零容量的短字符串。
短串模式
当源是短字符串时,代码是相同的:复制字节,并将源字节清零。在短模式下,没有自引用指针,因此复制字节是正确的算法。
现在确实在“短模式”下,源的 3 个字的清零可能会seem不必要,但要做到这一点,必须在长模式下检查长/短位和零字节。由于偶尔会发生分支错误预测(破坏管道),因此执行此检查和分支实际上比仅将 3 个字归零更昂贵。
这是 libc++ 的优化 x86(64 位)程序集string
移动构造函数。
std::string
test(std::string& s)
{
return std::move(s);
}
__Z4testRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE: ## @_Z4testRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq 16(%rsi), %rax
movq %rax, 16(%rdi)
movq (%rsi), %rax
movq 8(%rsi), %rcx
movq %rcx, 8(%rdi)
movq %rax, (%rdi)
movq $0, 16(%rsi)
movq $0, 8(%rsi)
movq $0, (%rsi)
movq %rdi, %rax
popq %rbp
retq
.cfi_endproc
(没有分支!)
<aside>
短字符串的内部缓冲区的大小也针对移动成员进行了优化。内部缓冲区与“长模式”所需的 3 个字“联合”,以便sizeof(string)
不需要比长模式更多的空间。尽管这个紧凑sizeof
(3个主要实现中最小的),libc++在64位架构上拥有最大的内部缓冲区:22char
.
小的sizeof
转换为更快的移动成员,因为所有这些成员所做的只是对象布局的复制和零字节。
See 这个 Stackoverflow 答案有关内部缓冲区大小的更多详细信息。
</aside>
Summary
所以综上所述,在“长模式”下需要将source设置为空字符串来转移指针的所有权,并且also出于性能原因,在短模式下是必要的,以避免管道损坏。
我对 libstdc++ 实现没有评论,因为我没有编写该代码,而且您的问题无论如何已经做得很好了。 :-)