文字列の参照はstd::string_view!
今回は C++17 で導入されたstd::string_view
を紹介しようと思います。名前通り文字列のビューということで、文字列の読み取り専用の型です。使い方としては、例えば以下のように文字列を関数に渡して出力したい時ですね。
void printStringView(std::string_view str) {
std::cout << str << '\n';
}
std::string_view
はどの型の文字列からも作成できるのが特徴です。↑の関数には以下のように全型の文字列が渡せます。
int main() {
std::string s{ "foo" };
std::string_view sv { s };
printStringView(s.c_str()); // C の文字列を渡す
printStringView(s); // std::string を渡す
printStringView(sv); // 他の std::string_view を渡す
return 0;
}
std::string_view
は内部的には「文字列を参照するポインタ」と「文字数」を持つような実装になっていて、元の文字列のコピーが発生しないので、パフォーマンスが最適です。
しかも読み取り専用なので、安全に利用できます。
ここまでの説明を聞いて、「え?const std::string&
で渡せばよくね?」と思ったそこのあなた!私も最初はそう思いました。
確かに、std::string_view
が導入される前は以下のようにどの型の文字列もstd::string
の参照型で渡せていました。
void printConstStringRef(const std::string& str) {
std::cout << str << '\n';
}
上記のように3つの型の文字列を渡すとどうなるかを見てみましょう。
int main() {
std::string s{ "foo" };
std::string_view sv { s };
printConstStringRef(s.c_str()); // C の文字列を渡す
printConstStringRef(s); // std::string を渡す
printConstStringRef(std::string(sv)); // 他の std::string_view を渡す
return 0;
}
-
std::string
を渡すと、コピーが発生しないです。ここはstd::string_view
とパフォーマンスが変わらないです。 -
C
の文字列を渡すと、参照できるstd::string
型の変数がないため、一時的な変数が作成され、文字列がコピーされます。 -
std::string_view
はそのまま渡せないせいで、明示的にstd::string
型に変換してから渡さないといけないため、その変換で必然的にコピーが発生します。
std::string_view
の場合は、どのケースもコピーが発生しないのに、const std::string&
の場合は、std::string
以外のケースはコピーが発生します。このユースケースでは明らかにstd::string_view
の方がいいですね。
後、std::string_view
は読み取り専用なので、本体の中身は変えられないですが、substr
、remove_prefix
、remove_suffix
というメンバー関数でその文字列の一部を抜粋することができます。
でも注意しないといけないのは、std::string_view
は参照とポインタと同じということです。以下のように本体の文字列がスコープから消えた後に使うと未定義動作になります。
std::string getName() {
return "fooooooooooooooo";
}
int main() {
std::string_view name { getName() }; // getName() の戻り値は一時オブジェクトでこの行の後に消えます。
std::cout << name << '\n'; // 未定義動作
return 0;
}
未定義動作ということで、↑のコードをいくつかのコンパイラで試してみたのですが、clang
でもgcc
でも文字化けしていました。
さらに、std::string_view
を作ってから本体を変えたりすると、そのstd::string_view
が無効になるため、その後使う際も未定義動作になります。
int main() {
std::string s { "fooooooooooooooo" };
std::string_view sv { s }; // sv は s のビューとして作成される
s = "bar"; // s が変えられたことによって sv が無効になる
std::cout << sv << '\n'; // 未定義動作
return 0;
}
実行してみると、"bar" ではなく "baroooooooooooo" が出力されます。
でも一度無効になったstd::string_view
は以下のようにまた有効にできます。
int main() {
std::string s { "foobar" };
std::string_view sv { s }; // sv は s のビューとして作成される
s = "bar"; // s が変えられたことによって sv が無効になる
sv = s; // sv に s が代入されるここで sv がまた有効になる
std::cout << sv << '\n'; // ちゃんと「bar」が出力される
return 0;
}
こちらを実行してみると、ちゃんと "bar" が出力されます。
纏めると、どの型の文字列でも読み取り専用で何かに渡したい時はstd::string_view
を使いましょう!
Discussion