我认为,从std::string
char 指针初始化NULL
是未定义的行为。所以,这里是构造函数的替代版本,其中mStdString
是std::string
类型的成员变量:
void MyClass::MyClass(const char *cstr) :
mStdString( cstr ? cstr : "")
{}
void MyClass::MyClass(const char *cstr) :
mStdString(cstr ? std::string(cstr) : std::string())
{}
void MyClass::MyClass(const char *cstr)
{
if (cstr) mStdString = cstr;
// else keep default-constructed mStdString
}
class MyClass
内的编辑,构造函数声明:
MyClass(const char *cstr = NULL);
从可能的std::string
指针中初始化NULL
的最佳或最恰当的方法是哪种或其他可能的方法?为什么?不同的C ++标准有什么不同?假设正常发布构建优化标志。
我正在寻找一个答案,解释为什么一种方式是正确的方式,或一个带有参考链接的答案(这也适用于答案是“无关紧要”),而不仅仅是个人意见(但如果你必须,至少只做一个评论)。
答案 0 :(得分:20)
最后一个是愚蠢的,因为它没有使用初始化。
前两个在语义上完全相同(想想c_str()
成员函数),所以更喜欢第一个版本,因为它是最直接和最惯用的,也是最容易阅读的。
(如果std::string
有一个constexpr
默认构造函数,那么会会出现语义差异,但它不会。但仍然是可能的 std::string()
与std::string("")
不同,但我不知道这样做的任何实现,因为它似乎没有多大意义。另一方面,流行的小字符串优化现在意味着两个版本可能不执行任何动态分配。)
更新:正如@Jonathan指出的那样,两个字符串构造函数可能会执行不同的代码,如果这对您很重要(尽管它确实不应该),您可能会考虑第四个版本:
: cstr ? cstr : std::string()
可读和默认构建。
第二次更新:但更喜欢cstr ? cstr : ""
。正如您在下面看到的,当两个分支都调用相同的构造函数时,可以使用条件移动和没有分支来非常有效地实现。 (所以这两个版本确实生成了不同的代码,但第一个代码更好。)
对于咯咯笑,我在x86_64上通过Clang 3.3和-O3
运行两个版本,对于像您这样的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
,但空字符串版本不使用任何跳转,只有条件移动(因为调用相同的构造函数,只有两个不同的参数)。当然,这是一个完全没有意义,不可移植和不可转移的观察,但它只是表明编译器并不总是需要你想象的那么多帮助。只需编写看起来最好的代码。
答案 1 :(得分:2)
首先,你是对的,来自http://www.cplusplus.com/reference/string/string/string/:
如果s是空指针,如果n == npos,或者[first,last]指定的范围无效,则会导致未定义的行为。
此外,它取决于NULL指针对您的意义。我认为它与你的空字符串相同。
我会选择第一个,因为它是我读得最好的那个。第一种解决方案和第二种方案是相同如果您的字符串为const
,则第三个将无效。
答案 2 :(得分:1)
假设您对cstr == NULL
产生空mStdString
感到满意,我认为第一个可能是最好的。
如果没有其他内容,如果mStdString
为const
,则您提供的第三个选项无效。中间选项受益于C ++ 11下的“移动语义”,但不太明显是最优或合理的。
所以,我的投票与第一个选项一致。
答案 3 :(得分:0)
虽然这可能不是一个真正的答案(特别是当你提出问题时) - 但它太长而不适合作为评论并且其中的代码不会在评论中出现。我完全希望得到投票,并且不得不删除这篇文章 - 但我觉得不得不说些什么。
为什么初始化char *
为NULL - 如果是这样,你不能将它推送到调用者以了解这种情况 - 例如传递空字符串,或"unknown"
或"(null)"
视情况而定。
换句话说,就像这样:
void MyClass::MyClass(const char *cstr)
{
assert(cstr != NULL); // or "throw cstr_must_not_be_null;" or some such.
mStdString = cstr;
}
(在初始化列表中可能有一些聪明的方法可以做到这一点,但是我不知道如何正确地做到这一点)。
我对一个人并不热衷于将NULL作为字符串参数的输入而不是“这真的不存在” - 如果那是你实际上想要复制的内容,那么你应该有一个{{ 1}}说“不存在”,或指向boolean
的指针,如果不存在字符串,则可以为NULL。