29

std::string我相信,从NULL char指针初始化是未定义的行为。因此,这里是构造函数的替代版本,其中mStdString是 type 的成员变量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++ 标准有什么不同吗?假设正常发布构建优化标志。

我正在寻找一个解释为什么一种方法是正确方法的答案,或者一个带有参考链接的答案(如果答案是“无关紧要”,这也适用),而不仅仅是个人意见(但如果你必须,至少让它只是一个评论)。

4

4 回答 4

20

最后一个是愚蠢的,因为它尽可能不使用初始化。

前两个在语义上完全相同(想想c_str()成员函数),所以更喜欢第一个版本,因为它最直接、最惯用,也最容易阅读。

(如果有一个默认的构造函数,会有语义上的差异,但它没有。不过,它可能与不同,但我不知道有任何实现,因为它似乎没有很多有道理。另一方面,现在流行的小字符串优化意味着这两个版本可能不会执行任何动态分配。)std::stringconstexprstd::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,但空字符串版本不使用任何跳转,只使用条件移动(因为调用了相同的构造函数,只是使用了两个不同的参数)。当然,这是一个完全没有意义、不可移植和不可转移的观察,但它只是表明编译器并不总是需要你想象的那么多帮助。只需编写看起来最好的代码。

于 2013-07-04T07:47:41.693 回答
6

首先,你是对的,来自http://www.cplusplus.com/reference/string/string/string/

如果 s 是空指针,如果 n == npos,或者 [first,last) 指定的范围无效,则会导致未定义的行为。

此外,这取决于 NULL 指针对您意味着什么。我认为它与您的空字符串相同。

我会选择第一个,因为它是我读得最好的。第一个解决方案和第二个解决方案相同。如果您的字符串是,第三个将不起作用const

于 2013-07-04T07:51:32.083 回答
1

cstr == NULL假设您对产生一个 empty感到满意mStdString,我认为第一个可能是最好的。

mStdString如果没有别的,您提供的第三个选项在is时不起作用const。中间选项受益于 C++11 下的“移动语义”,但不太明显是最优或合理的。

所以,我的投票赞成第一个选项。

于 2013-07-04T07:49:18.420 回答
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 作为字符串参数的输入,而不是“这确实不存在” - 如果这就是您实际尝试复制的内容,那么您应该boolean说“不存在” t 存在”,或者指向 a 的指针,std::string如果不存在字符串,则该指针可以为 NULL。

于 2013-07-04T08:47:57.593 回答