簡體   English   中英

從可能的NULL char指針初始化std :: string

[英]Initialize std::string from a possibly NULL char pointer

我相信,從NULL char指針初始化std::string是未定義的行為。 所以,這里是構造函數的替代版本,其中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);

從可能的NULL指針初始化std::string的最佳或最正確的方法中,哪一個或者可能是其他東西,為什么? 不同的C ++標准有什么不同? 假設正常發布構建優化標志。

我正在尋找一個答案,解釋為什么一種方式是正確的方式,或一個帶有參考鏈接的答案(這也適用於答案是“無關緊要”),而不僅僅是個人意見(但如果你必須,至少讓它只是一個評論)。

最后一個是愚蠢的,因為它可以使用初始化。

前兩個在語義上完全相同(想想c_str()成員函數),所以更喜歡第一個版本,因為它是最直接和最慣用的,也是最容易閱讀的。

會有一個語義區別,如果std::stringconstexpr默認構造函數,但它不會。不過,這可能std::string()是不同std::string("")但我不我不知道這樣做的任何實現,因為它似乎沒有多大意義。另一方面,流行的小字符串優化現在意味着兩個版本可能不會執行任何動態分配。)


更新:正如@Jonathan指出的那樣,兩個字符串構造函數可能會執行不同的代碼,如果這對您很重要(盡管它確實不應該),您可能會考慮第四個版本:

: cstr ? cstr : std::string()

可讀和默認構造。


第二次更新:但是更喜歡cstr ? cstr : "" cstr ? cstr : "" 如下所示,當兩個分支調用相同的構造函數時,可以使用條件移動和無分支非常有效地實現。 (所以這兩個版本確實生成了不同的代碼,但第一個版本更好。)


對於咯咯笑,我在x86_64上通過Clang 3.3和-O3運行兩個版本,用於struct foo; 喜歡你和函數foo bar(char const * p) { return p; } 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 ,但是空字符串版本不使用任何跳轉,只使用條件移動(因為調用相同的構造函數,只有兩個不同的參數) 。 當然,這是一個完全沒有意義,不可移植和不可轉移的觀察,但它只是表明編譯器並不總是需要你想象的那么多幫助。 只需編寫看起來最好的代碼。

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

如果s是空指針,如果n == npos,或者[first,last)指定的范圍無效,則會導致未定義的行為。

此外,它取決於NULL指針對您來說意味着什么。 我認為它與你的空字符串相同。

我會選擇第一個,因為它是我讀得最好的那個。 第一種解決方案和第二種方案是相同 如果你的字符串是const第三個將無效。

假設你對mStdString cstr == NULL感到滿意,產生一個空的mStdString ,我認為第一個可能是最好的。

如果沒有別的,如果mStdStringconst那么你提供的第三個選項不起作用。 中間選項受益於C ++ 11下的“移動語義”,但不太明顯是最優或合理的。

所以,我的投票與第一個選項一致。

雖然這可能不是一個真正的答案(特別是當你提出問題時) - 但是它太長而不適合作為評論並且其中的代碼不會在評論中出現。 我完全希望得到投票,並且不得不刪除這篇文章 - 但我覺得不得不說些什么。

為什么初始化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來說“不要“存在”,或指向std::string的指針,如果不存在std::string ,則該指針可以為NULL。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM