yohhoyの日記

技術的メモをしていきたい日記

dataメンバ関数:std::string vs. std::string_view

C++1z(C++17)標準ライブラリ文字列std::stringと文字列ビューstd::string_viewにおいて、dataメンバ関数が返すメモリ領域とヌル終端文字列の関係についてメモ。

まとめ:

  • stringに “非const版”dataメンバ関数 が追加され、変更可能なヌル終端文字列を返す。(→id:yohhoy:20160327
  • string_viewdataメンバ関数は、NUL文字('\0')で終端される保証のない読取専用メモリ領域を返す。
    • string_viewは文字列の “ビュー” に過ぎないため、有効範囲[data(), data() + length())を超えた*(data() + length())はNUL文字でない可能性がある。*1
    • string/string_viewdataメンバ関数戻り値は意味が異なるため、文字列参照型(const string&)から文字列ビュー型(string_view)への単純置換には注意が必要。(→id:yohhoy:20171113
    • ヌル終端文字列の取得にstring::c_strメンバ関数を利用していれば、存在しないstring_view::c_str呼び出しがコンパイルエラーとして検出される。C++11以降のstring::dataメンバ関数仕様に依存したソースコードのみ影響を受ける。(→id:yohhoy:20120601
const修飾あり const修飾なし
string
(C++14以前)
読取専用ヌル終端文字列
戻り値型=const char*
読取専用ヌル終端文字列
戻り値型=const char*
string
(C++1z以降)
読取専用ヌル終端文字列
戻り値型=const char*
変更可能なヌル終端文字列
戻り値型=char*
string_view 読取専用メモリ領域
NUL文字終端の保証なし
戻り値型=const char*
読取専用メモリ領域
NUL文字終端の保証なし
戻り値型=const char*

ヌル終端文字列を要求するレガシー関数(下記コードではstd::puts)に対して、string_view::dataメンバ関数を組み合わせると予期しない結果をもたらす。

// 文字列参照型の引数
void func(const std::string& s) {
  std::puts(s.data());   // OK: "DEF"を出力
  // または
  std::puts(s.c_str());  // OK: "DEF"を出力
}

std::string str = "abcDEFghi";
func(str.substr(3, 3));  // 部分文字列"DEF"を渡す
// C++1z(C++17)
// 文字列ビュー型の引数
void func_strview(std::string_view sv) {
  std::puts(sv.data());  // NG: "DEFghi"を出力

  std::string s{sv};     // std::stringへコピーすれば...
  std::puts(s.c_str());  // OK: "DEF"を出力
}

std::string_view sv1 = "abcDEFghi";
func(sv1.substr(3, 3));  // 部分文字列ビュー"DEF"を渡す

関連URL

*1:string_view が参照する先が通常のヌル終端文字列や std::string オブジェクトであれば、“ビュー” 範囲をオーバーランしてもいずれはNUL文字で終端される。一方、string_view がヌル終端文字列でない文字シーケンス(例:固定フィールド長のchar配列)を参照する場合、ビュー範囲のオーバーランは未定義動作(undefined behavior)を引き起こす。