最后一个是愚蠢的,因为它尽可能不使用初始化。
前两个在语义上完全相同(想想c_str()
成员函数),所以更喜欢第一个版本,因为它最直接、最惯用,也最容易阅读。
(如果有一个默认的构造函数,会有语义上的差异,但它没有。不过,它可能与不同,但我不知道有任何实现,因为它似乎没有很多有道理。另一方面,现在流行的小字符串优化意味着这两个版本可能不会执行任何动态分配。)std::string
constexpr
std::string()
std::string("")
更新:正如@Jonathan 指出的那样,这两个字符串构造函数可能会执行不同的代码,如果这对您很重要(尽管它确实不应该),您可能会考虑第四个版本:
: cstr ? cstr : std::string()
可读和默认构造。
第二次更新:但更喜欢cstr ? cstr : ""
. 正如您在下面看到的,当两个分支都调用相同的构造函数时,可以使用条件移动和无分支非常有效地实现这一点。(所以这两个版本确实生成了不同的代码,但第一个更好。)
对于咯咯笑,我已经通过 Clang 3.3 运行了这两个版本-O3
,在 x86_64 上运行了一个struct foo;
和你一样的函数foo bar(char const * p) { return p; }
:
默认构造函数 ( std::string()
):
.cfi_offset r14, -16
mov R14, RSI
mov RBX, RDI
test R14, R14
je .LBB0_2
mov RDI, R14
call strlen
mov RDI, RBX
mov RSI, R14
mov RDX, RAX
call _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm
jmp .LBB0_3
.LBB0_2:
xorps XMM0, XMM0
movups XMMWORD PTR [RBX], XMM0
mov QWORD PTR [RBX + 16], 0
.LBB0_3:
mov RAX, RBX
add RSP, 8
pop RBX
pop R14
ret
空字符串构造函数(""
):
.cfi_offset r14, -16
mov R14, RDI
mov EBX, .L.str
test RSI, RSI
cmovne RBX, RSI
mov RDI, RBX
call strlen
mov RDI, R14
mov RSI, RBX
mov RDX, RAX
call _ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm
mov RAX, R14
add RSP, 8
pop RBX
pop R14
ret
.L.str:
.zero 1
.size .L.str, 1
在我的情况下,它甚至会""
生成更好的代码:两个版本都调用strlen
,但空字符串版本不使用任何跳转,只使用条件移动(因为调用了相同的构造函数,只是使用了两个不同的参数)。当然,这是一个完全没有意义、不可移植和不可转移的观察,但它只是表明编译器并不总是需要你想象的那么多帮助。只需编写看起来最好的代码。