C++11で導入された range-based for 構文を用いて、文字列を一文字づつ走査する方法。*1
文字列リテラル
文字列リテラルはconst char
配列型(→id:yohhoy:20150213)であるため、range-based for 構文による要素走査が可能。ただし文字列リテラル末尾に自動追加*2されるNUL終端文字('\0'
)も走査対象となることに留意。
#include <cstdio> for (char c: "Hello") { // const char[6]型 std::printf("(%d)", c); } // (72)(101)(108)(108)(111)(0)
std::string型
C++標準ライブラリの文字列型(std::string
)はbegin
/end
メンバ関数を提供するため、range-based for 構文による要素走査が可能。下記コードのようにユーザ定義リテラル""s
から構築された文字列オブジェクトはNUL終端文字を含まない*3。一方、実行時に文字列型の一時オブジェクト構築/破壊コストが生じる。
#include <cstdio> #include <string> using namespace std::string_literals; for (char c: "Hello"s) { // std::string型 std::printf("(%d)", c); } // (72)(101)(108)(108)(111)
std::string_view型
C++1z(C++17)標準ライブラリに導入されるstd::string_view
型もbegin
/end
メンバ関数を提供するため、range-based for 構文による要素走査が可能。下記コードのようにユーザ定義リテラル""sv
から構築されたstring_view
オブジェクトはNUL終端文字を含まない*4。またstring_view
は文字列を “参照” するだけの軽量なデータ型のため、string
オブジェクト構築/破壊に比べ実行時コストが小さくなることを期待できる。
// C++1z(C++17) #include <cstdio> #include <string_view> using namespace std::string_view_literals; for (char c: "Hello"sv) { // std::string_view型 std::printf("(%d)", c); } // (72)(101)(108)(108)(111)
ノート:C++コンパイラの最適化処理により、一時オブジェクトのコンストラクタ/デストラクタ呼び出しは省略される可能性はある。GCC 7.0(HEAD)/-std=c++1z, -03オプションでアセンブリ出力を確認したところ、一時string
オブジェクトのコンストラクタ/デストラクタ呼び出しは削除されたが、グローバルdelete演算子(operator delete(void*)
)呼び出しだけは残っていた。一方のstring_view
版では同演算子の呼び出しも含めて最適化されていた。*5
関連URL
*1:C++言語仕様では文字列リテラルの文字集合は処理系定義(implementation defined)となっている。本記事においてはASCII文字集合(もしくはUTF-8等の互換文字集合)かつ一文字=1バイト(char)の幸せな世界を前提とする。
*2:C++14 2.14.5/p15:"After any necessary concatenation, in translation phase 7 (2.2), '\0' is appended to every string literal so that programs that scan a string can find its end."
*3:厳密には、ユーザ定義リテラル ""s に与えた文字列リテラル末尾に自動追加されるNUL終端文字のみが除外される。"a\0b\0"s のように文字列リテラル中にNUL終端文字を明示した場合、本文中コードの実行結果は (7)(0)(8)(0) となる。
*4:厳密には、ユーザ定義リテラル ""sv に与えた文字列リテラル末尾に自動追加されるNUL終端文字のみが除外される。
*5:string版:https://gist.github.com/yohhoy/76b9e0d7c30ccd81317fecb71c734847 string_view版:https://gist.github.com/yohhoy/ccade95cdbb345611bce8dcfc20847e7