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

hyd*_*yde 22 c++ null stdstring

我相信,std::stringNULL char指针初始化是未定义的行为.所以,这里是构造函数的替代版本,其中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
}
Run Code Online (Sandbox Code Playgroud)

编辑,构造函数声明里面class MyClass:

MyClass(const char *cstr = NULL);
Run Code Online (Sandbox Code Playgroud)

其中哪一个,或者可能是其他东西,是std::string从可能NULL指针初始化的最佳或最正确的方法,为什么?不同的C++标准有什么不同?假设正常发布构建优化标志.

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

Ker*_* SB 20

最后一个是愚蠢的,因为它可以使用初始化.

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

(这里是一个语义的区别,如果std::string有一个constexpr默认的构造函数,但它不会.不过,这是有可能的是std::string()来自不同的std::string(""),但我不知道这样做的任何实现,因为似乎使很多不另一方面,流行的小字符串优化现在意味着两个版本可能都不会执行任何动态分配.)


更新:正如@Jonathan指出的那样,两个字符串构造函数可能会执行不同的代码,如果这对您很重要(尽管它确实不应该),您可能会考虑第四个版本:

: cstr ? cstr : std::string()
Run Code Online (Sandbox Code Playgroud)

可读和默认构造.


第二次更新:但更喜欢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
Run Code Online (Sandbox Code Playgroud)

空字符串构造函数(""):

    .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
Run Code Online (Sandbox Code Playgroud)

在我的情况下,它甚至会""产生更好的代码:两个版本都调用strlen,但是空字符串版本不使用任何跳转,只使用条件移动(因为调用相同的构造函数,只有两个不同的参数).当然,这是一个完全没有意义,不可移植和不可转移的观察,但它只是表明编译器并不总是需要你想象的那么多帮助.只需编写看起来最好的代码.

  • @JonathanWakely:对,在代码中不一样,但在语义上......(和空字符串上的`strlen`仍然相当不错).嘿,那怎么样?cstr?cstr:string()`? (3认同)
  • 前两个不完全相同,即使是IMHO语义,在其中一个中调用`strlen`,编译器可能在编译时替换它,但你仍然为`std :: string()调用不同的代码路径`和`std :: string("",0)`.由于您知道内容为空,因此调用默认构造函数比传递,计数和复制零长度`char`数组更有意义 (2认同)
  • @JonathanWakely:我刚刚发现简单的`cstr?cstr:""`可以用条件移动非常巧妙地实现,所以使用*same*构造函数实际上是一个福音. (2认同)

Xaq*_*aqq 6

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

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

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

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