web-dev-qa-db-ja.com

NULLの可能性があるcharポインタからstd :: stringを初期化します

NULLからstd::stringを初期化するcharポインタは未定義の動作だと思います。したがって、ここにコンストラクタの代替バージョンがあります。ここで、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++標準ごとに異なりますか?通常のリリースビルド最適化フラグを想定しています。

個人的な意見だけでなく、方法が正しい方法である理由の説明付きの回答、または参照リンク付きの回答(回答が「問題ではない」場合にも適用されます)を探しています)必要な場合は、少なくともコメントにするだけです)

23
hyde

最後のものは、可能であれば初期化を使用しないため、ばかげています。

最初の2つは意味的に完全に同一です(c_str()メンバー関数と考えてください)。最初のバージョンが最も直接的で慣用的であり、最も読みやすいので、最初のバージョンをお勧めします。

(_std::string_にconstexprのデフォルトコンストラクタがある場合、は意味上の違いがありますが、そうではありません。それでも可能std::string()std::string("")とは異なりますが、あまり意味がないように見えるため、これを行う実装はわかりません。一方、最近人気のある小さな文字列の最適化は、両方のバージョンがおそらく動的割り当てをしないことを意味します。)


更新:@Jonathanが指摘するように、2つの文字列コンストラクターはおそらく異なるコードを実行し、それが重要な場合(実際にはそうではありませんが)、4番目のバージョン:

_: cstr ? cstr : std::string()
_

可読性とデフォルト構築の両方。


2回目の更新:ただし、_cstr ? cstr : ""_をお勧めします。以下に示すように、両方のブランチがsameコンストラクターを呼び出す場合、これは条件付きの移動を使用してブランチを使用せずに非常に効率的に実装できます。 (つまり、2つのバージョンは実際に異なるコードを生成しますが、最初のバージョンの方が優れています。)


笑い声の場合、x86_64で_-O3_を使用して、あなたのような_struct foo;_と関数foo bar(char const * p) { return p; }に対して、Clang 3.3から両方のバージョンを実行しました。

デフォルトのコンストラクター(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
_

私の場合、_""_がbetterコードを生成するようにも見えます:両方のバージョンがstrlenを呼び出しますが、空文字列バージョンは使用しませんジャンプ、条件付き移動のみ(同じコンストラクターが呼び出されるため、2つの異なる引数を使用)。もちろん、これは完全に無意味で、移植性がなく、譲渡できない観察結果ですが、コンパイラが必ずしもあなたが思っているほど多くの助けを必要としないことを示しています。最適なコードを記述してください。

20
Kerrek SB

まず最初に、あなたは正しいです http://www.cplusplus.com/reference/string/string/string/ から:

Sがnullポインターの場合、n == nposの場合、または[first、last)で指定された範囲が無効な場合、未定義の動作が発生します。

また、NULLポインタが何を意味するかによっても異なります。空の文字列と同じだと思います。

私が一番よく読んだので、私は最初のものに行きます。最初のソリューションと2番目のソリューションは同じです。文字列がconstの場合、3つ目は機能しません。

4
Xaqq

cstr == NULLが空のmStdStringを生成することに満足しているとすれば、おそらく最初のものが最も良いと思います。

他に何もない場合、mStdStringconstの場合、指定した3番目のオプションは機能しません。真ん中のオプションは、C++ 11での「セマンティクスの移動」から利益を得ますが、明らかに最適または合理的ではありません。

だから、私の投票は最初のオプションに行きます。

1
Joe Z

これは本当に回答とは限りませんが(特に質問を定式化した場合)、コメントとして収めるには長すぎて、コメントにorkしないコードが含まれています。私は全面的に反対票を投じることを期待しており、この投稿を削除する必要があります-しかし、私は何かを言わざるを得ませんでした。

なぜ初期化_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で「doesn」と言う必要があります。 「存在しない」、または_std::string_へのポインタ。文字列が存在しない場合はNULLにすることができます。

0
Mats Petersson