从可能的NULL char指针初始化std :: string

时间:2013-07-04 07:41:51

标签: c++ null stdstring

我认为,从std::string char 指针初始化NULL是未定义的行为。所以,这里是构造函数的替代版本,其中mStdStringstd::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 个答案:

答案 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感到满意,我认为第一个可能是最好的。

如果没有其他内容,如果mStdStringconst,则您提供的第三个选项无效。中间选项受益于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。