C++ 从可能为空的字符指针初始化std::string

C++ 从可能为空的字符指针初始化std::string,c++,null,stdstring,C++,Null,Stdstring,我认为,从NULLchar指针初始化std::string是未定义的行为。因此,这里是构造函数的替代版本,其中mStdString是std::string类型的成员变量: void MyClass::MyClass(const char *cstr) : mStdString( cstr ? cstr : "") {} void MyClass::MyClass(const char *cstr) : mStdString(cstr ? std::string(cstr) :

我认为,从
NULL
char指针初始化
std::string
是未定义的行为。因此,这里是构造函数的替代版本,其中
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
}
编辑,类MyClass中的构造函数声明:

MyClass(const char *cstr = NULL);
以下哪种或其他方式是从可能的
NULL
指针初始化
std::string
的最佳或最合适的方法,为什么?不同的C++标准是否不同?假设正常发布构建优化标志


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

最后一个答案很愚蠢,因为它在可能的情况下不使用初始化

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

(如果
std::string
有一个
constepr
默认构造函数,则会有语义上的差异,但它没有。尽管如此,
std::string()
可能不同于
std::string(“”)
,但我不知道有什么实现可以做到这一点,因为这似乎没有多大意义。另一方面,现在流行的小字符串优化意味着两个版本可能都不会执行任何动态分配。)


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

: cstr ? cstr : std::string()
可读和默认构造


第二次更新:但更喜欢cstr?cstr:“”。正如您在下面看到的,当两个分支调用同一个构造函数时,可以使用条件移动而不使用分支非常有效地实现这一点。(因此这两个版本确实生成不同的代码,但第一个版本更好。)


对于giggles,我已经通过Clang3.3运行了这两个版本,在x86_64上运行了
-O3
,用于
结构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
空字符串构造函数(
):

    .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
,但空字符串版本不使用任何跳转,只使用条件移动(因为调用相同的构造函数,只是使用两个不同的参数)。当然,这是一个完全没有意义的、不可移植和不可转移的观察结果,但它只是表明编译器并不总是像您所想的那样需要帮助。只要编写看起来最好的代码。

假设您对生成空的
mStdString
感到满意,我认为第一个可能是最好的

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


因此,我的投票支持第一种选择。

首先,你是对的,来自:

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

此外,它还取决于空指针对您的意义。我假设它与空字符串对您的意义相同


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

,那么第三个解决方案就不起作用,而这可能不是一个真正的答案(尤其是在你制定问题时)-但是它太长了,不适合作为评论,而且里面有代码,在评论中不起作用。我完全希望被否决,不得不删除这篇文章-但我觉得不得不说点什么

为什么初始化
char*
是空的?如果是这样,您不能将其推送到调用方以了解这种情况吗?例如传递一个空字符串,或
“unknown”
“(NULL)”
(视情况而定)

换句话说,类似这样的事情:

void MyClass::MyClass(const char *cstr) 
{ 
    assert(cstr != NULL);   // or "throw cstr_must_not_be_null;" or some such. 
    mStdString = cstr;
}
(在初始值设定项列表中可能有一些聪明的方法可以做到这一点,但我不想费心去弄清楚如何正确地做到这一点)


例如,我不喜欢以“这确实不存在”以外的任何方式将NULL作为字符串参数的输入,如果这是您实际尝试复制的,那么您应该使用
布尔值来表示“不存在”,或指向
std::string
的指针,如果不存在字符串,则该指针可以为NULL。

@Plasmah好吧,我希望它在char指针为NULL时执行代码段所做的操作。离题:这就是
std::string
不接受NULL指针的原因。我想知道随意处理NULL指针并清空sam中的字符串是否是个好主意e、 我个人会在调用代码中明确说明这一点,而不是将其隐藏在某个构造函数中。@KerrekSB不太离题。这可能是这里最相关的信息。我建议您将其添加到您的答案中。@DanielDaranas:OP已明确要求在这个问题上使用
//no lint
标志,并且不希望出现错误概念批判:-S(换句话说,我只能为你装枪对准你的脚,但你必须自己扣动扳机。)@KerrekSB除了问题本身,我通常不在乎OP想要什么或说什么。一旦发布,这是一个StackOverflow问题。它属于StackOverflow社区,而不是他。“假设合理的优化”我们可以假设