std::stringはもう古い?C++における次世代文字列操作ツールstd::basic_string_view


本記事では、std::basic_string_view における主要な演算子とその使用方法について分かりやすく解説します。

比較演算子

std::basic_string_view は、文字列の比較に使用できる以下の比較演算子を備えています。

  • >=: 大なりまたは等価比較。1つの std::basic_string_view オブジェクトが別のオブジェクトよりも文字列として大きいか、または等しいかどうかを検査します。
  • >: 大なり比較。1つの std::basic_string_view オブジェクトが別のオブジェクトよりも文字列として大きいかどうかを検査します。
  • <=: 小なりまたは等価比較。1つの std::basic_string_view オブジェクトが別のオブジェクトよりも文字列として小さいか、または等しいかどうかを検査します。
  • <: 小なり比較。1つの std::basic_string_view オブジェクトが別のオブジェクトよりも文字列として小さいかどうかを検査します。
  • !=: 非等価性比較。2つの std::basic_string_view オブジェクトが異なる文字列かどうかを検査します。
  • ==: 等価性比較。2つの std::basic_string_view オブジェクトが同じ文字列かどうかを検査します。

例:

#include <string_view>

int main() {
  std::string_view s1 = "Hello";
  std::string_view s2 = "World";
  std::string_view s3 = "Hello";

  if (s1 == s2) {
    std::cout << "s1 と s2 は同じ文字列です" << std::endl;
  } else {
    std::cout << "s1 と s2 は異なる文字列です" << std::endl;
  }

  if (s1 < s2) {
    std::cout << "s1 は s2 より文字列として小さいです" << std::endl;
  } else {
    std::cout << "s1 は s2 より文字列として大きくありません" << std::endl;
  }

  if (s1 <= s3) {
    std::cout << "s1 は s3 より文字列として小さく、または等しいです" << std::endl;
  } else {
    std::cout << "s1 は s3 より文字列として大きいです" << std::endl;
  }

  return 0;
}

このコードは、以下の出力を生成します。

s1 と s2 は異なる文字列です
s1 は s2 より文字列として小さいです
s1 は s3 より文字列として小さく、または等しいです

std::basic_string_view は、部分文字列の検索に使用できる以下の検索演算子を備えています。

  • find_last_of(s): 引数 s で指定された文字のいずれかが最後に現れる位置を返します。文字が見つからない場合は、std::basic_string_view::npos を返します。
  • find_first_of(s): 引数 s で指定された文字のいずれかが最初に現れる位置を返します。文字が見つからない場合は、std::basic_string_view::npos を返します。
  • rfind(s): 引数 s で指定された部分文字列が最後に現れる位置を返します。部分文字列が見つからない場合は、std::basic_string_view::npos を返します。
  • find(s): 引数 s で指定された部分文字列が最初に現れる位置を返します。部分文字列が見つからない場合は、std::basic_string_view::npos を返します。
#include <string_view>

int main() {
  std::string_view s = "Hello, World!";
  std::string_view sub1 = "World";
  std::string_view sub2 = "!";

  size_t pos1 = s.


#include <string_view>

int main() {
  std::string_view s1 = "Hello";
  std::string_view s2 = "World";
  std::string_view s3 = "Hello";

  if (s1 == s2) {
    std::cout << "s1 と s2 は同じ文字列です" << std::endl;
  } else {
    std::cout << "s1 と s2 は異なる文字列です" << std::endl;
  }

  if (s1 < s2) {
    std::cout << "s1 は s2 より文字列として小さいです" << std::endl;
  } else {
    std::cout << "s1 は s2 より文字列として大きくありません" << std::endl;
  }

  if (s1 <= s3) {
    std::cout << "s1 は s3 より文字列として小さく、または等しいです" << std::endl;
  } else {
    std::cout << "s1 は s3 より文字列として大きいです" << std::endl;
  }

  return 0;
}
s1 と s2 は異なる文字列です
s1 は s2 より文字列として小さいです
s1 は s3 より文字列として小さく、または等しいです
#include <string_view>

int main() {
  std::string_view s = "Hello, World!";
  std::string_view sub1 = "World";
  std::string_view sub2 = "!";

  size_t pos1 = s.find(sub1);
  if (pos1 != std::basic_string_view::npos) {
    std::cout << "部分文字列 " << sub1 << " は位置 " << pos1 << " に見つかりました" << std::endl;
  } else {
    std::cout << "部分文字列 " << sub1 << " は見つかりませんでした" << std::endl;
  }

  size_t pos2 = s.rfind(sub1);
  if (pos2 != std::basic_string_view::npos) {
    std::cout << "部分文字列 " << sub1 << " は位置 " << pos2 << " に見つかりました" << std::endl;
  } else {
    std::cout << "部分文字列 " << sub1 << " は見つかりませんでした" << std::endl;
  }

  size_t pos3 = s.find_first_of(sub2);
  if (pos3 != std::basic_string_view::npos) {
    std::cout << "文字 " << sub2 << " のいずれか 1 つが位置 " << pos3 << " に見つかりました" << std::endl;
  } else {
    std::cout << "文字 " << sub2 << " のいずれも見つかりませんでした" << std::endl;
  }

  size_t pos4 = s.find_last_of(sub2);
  if (pos4 != std::basic_string_view::npos) {
    std::cout << "文字 " << sub2 << " のいずれか 1 つが位置 " << pos4 << " に見つかりました" << std::endl;
  } else {
    std::cout << "文字 " << sub2 << " のいずれも見つかりませんでした" << std::endl;
  }

  return 0;
}
部分文字列 World は位置 7 に見つかりました
部分文字列 World は位置 7 に見つかりました
文字 ! は位置 10 に見つかりました
文字 ! は位置 10 に見つかりました
  • []: インデックスを使用して文字
  • +: 2つの std::basic_string_view オブジェクトを連結します。


以下に、std::basic_string_view の代替となる可能性のあるもの、およびそれぞれの長所と短所をいくつか示します。

C++ スタイルの文字列リテラル

C++ スタイルの文字列リテラルは、単純な文字列操作には軽量で効率的な方法です。

利点:

  • コンパイル時にメモリ割り当てが行われるため、ランタイムのパフォーマンスが優れている
  • シンプルで分かりやすい構文

短所:

  • 長い文字列には不向き
  • 変更不可。つまり、リテラル内で文字列を操作することはできません。
const char* s = "Hello, World!";

std::array

std::array は、固定長の要素を持つ固定サイズのコンテナーです。文字列を格納する場合、std::array<char, N> を使用して、N 個の文字で構成される文字列を表すことができます。

  • メモリ割り当てが事前にわかっているため、パフォーマンスが予測可能
  • C++ スタイルの文字列リテラルよりも柔軟。リテラル内で文字列を操作できます。
  • 長い文字列には不向き
  • std::basic_string_view ほど効率的ではない
std::array<char, 13> s = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '\0'};

std::vector

std::vector は、可変長の要素を持つ動的なコンテナーです。文字列を格納する場合、std::vector<char> を使用して、必要なだけ多くの文字を格納できる文字列を表すことができます。

  • 長い文字列に適している
  • 最も柔軟なオプション。文字列の作成、操作、破棄を自由に制御できます。
  • メモリ割り当てが動的に行われるため、フラグメンテーションが発生する可能性がある
  • std::basic_string_viewstd::array よりもパフォーマンスが劣る可能性がある
std::vector<char> s = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '\0'};

カスタム文字列クラス

独自の要件を満たすために、カスタム文字列クラスを作成することもできます。

  • 特定のニーズに合わせた機能を追加できる
  • 完全な制御が可能
  • テストとデバッグがより困難になる可能性がある
  • 複雑で時間のかかる作業
class MyString {
private:
  char* data;
  size_t size;

public:
  MyString(const char* s);
  ~MyString();

  // その他のメソッド
};

std::basic_string_view は、多くの場合において優れた選択肢ですが、状況によっては代替手段の方が適切な場合があります。上記で紹介した代替手段をそれぞれ検討し、要件に合致するものを選択してください。

  • 使いやすさ:使いやすさが重要な場合は、C++ スタイルの文字列リテラルを検討してください。
  • 柔軟性:柔軟性が重要な場合は、std::vector を検討してください。
  • パフォーマンス:パフォーマンスが重要な場合は、std::basic_string_view または C++ スタイルの文字列リテラルを検討してください。