C ++プログラミング言語は文字列処理をサポートしており、そのほとんどは標準ライブラリに実装されています。言語標準ではいくつかの文字列型が規定されており、Cから継承されたものもあれば、クラスやRAIIなどの言語機能を活用するように設計されたものもあります。これらのうち最もよく使われるのは でありstd::string、 はstd::string_view非所有文字列ビューに使用されます。
C++の初期バージョンには「低レベル」のC言語の文字列処理機能と規約しかなかったため、長年にわたって互換性のない文字列処理クラスの設計が複数考案され、現在でも代わりに使用されています。そのためstd::string、C++プログラマーは単一のアプリケーションで複数の規約を処理する必要がある場合があります。
型std::stringは 1998 年以来標準 C++ の主要な文字列データ型ですが、常に C++ の一部であったわけではありません。C から、最初の要素へのポインタによって処理されるヌル終端文字列を使用する慣習と、そのような文字列を操作する関数のライブラリが C から継承されました。現代の標準 C++ では、 のような文字列リテラルは依然としてヌル終端文字配列を表します。[ 1 ]"hello"
C++ クラスを使用して文字列型を実装すると、自動メモリ管理や境界外アクセスのリスク低減[ 2 ]、文字列の比較や連結のためのより直感的な構文など、いくつかの利点が得られます。そのため、このようなクラスを作成するのは非常に魅力的でした。長年にわたり、C++ アプリケーション、ライブラリ、フレームワークの開発者は、AT&Tの Standard Components ライブラリ (このような実装としては最初のもの、1983 年) [ 3 ]やCStringMicrosoft のMFC [ 4 ]やQStringQt [ 5 ] [ 6 ]など、独自の互換性のない文字列表現を作成してきました。
各ベンダーの文字列型は実装戦略や性能特性が異なるため、コードの変換や、ある型から別の型への代入さえも困難だった。
標準化された文字列がある一方でstd::string、レガシーアプリケーションには依然としてこのようなカスタム文字列型が含まれていることが多く、ライブラリはCスタイルの文字列を期待している場合があるため、C++プログラムで複数の文字列型を使用することを避けることは「事実上不可能」であり[ 1 ]、プログラマーはプロジェクトを開始する前に必要な文字列表現を決定する必要がある[ 4 ] 。 1991年のC++の歴史に関する回顧録で、C++の発明者であるビャルネ・ストロヴストルップは、C++ 1.0に標準文字列型(およびその他のいくつかの標準型)がなかったことが、開発中に犯した最悪の間違いだったと述べている。「それらの欠如により、誰もが車輪を再発明し、最も基本的なクラスに不必要な多様性が生じた」[ 3 ] 。
このクラスは、C++98std::string以降、テキスト文字列の標準表現となっています。このクラスは、比較、連結、検索と置換、部分文字列を取得する関数など、いくつかの典型的な文字列操作を提供します。 はC スタイルの文字列から構築でき、また C スタイルの文字列も から取得できます。[ 8 ]文字列を構成する個々の単位はchar型です。現代の使用法では、これらはしばしば「文字」ではなく、 UTF-8などのマルチバイト文字エンコーディングの一部です。std::string
import std ;std :: stringを使用します。int main () { string foo = "fighters" ; string bar = "stool" ; if ( foo != bar ) { std :: println ( "文字列が違います!" ); } foo += bar + " end" ; // foo は "fightersstool end" になりますstd :: println ( "{}" , foo ); // 出力}文字列は、演算子を使用して文字列リテラルから構築できます。たとえば、[ 9 ]std::literals::string_literals::operator""sstrings="Hello, world!"s;
コピーオンライト実装とは
string a = "hello!" ; string b = a ; // コピーコンストラクタは実際には の内容を にコピーしませんa。b代わりに、両方の文字列は内容を共有し、内容の参照カウントが増加します。実際のコピーは、文字の変更などの変更操作によって文字列の内容が変化するまで延期されます。
コピーオンライト戦略は、std::string有用な最適化であると見なされ、ほぼすべての実装で使用されたため、最初の C++ 標準で意図的に許可されました。[ 8 ]しかし、間違いもありました。特に、operator[]C コード(など)を簡単に移植できるようにするために、非 const 参照を返すようにしたためs[5] = 'a'、コピーをトリガーする必要がありました。マルチスレッドにより、最適化(参照カウントが 1 の場合にコピーしないなど)が失敗しました[ 10 ]。また、最新のプロセッサでは、参照カウントを調べたり変更したりするために必要なロックは、小さな文字列をコピーするよりもコストがかかります。[ 11 ]
このため、実装は、最初はMSVC、後にGCCでコピーオンライトから離れることになった。[ 12 ]この最適化は最終的にC++11で禁止された。[ 13 ]
現在、ほとんどの実装では、16バイト(または23バイト)未満の文字列を文字列オブジェクトに格納し、バッファを割り当てないスモールストリング最適化(SSO)が採用されています。 [ 14 ]文字列の驚くほど多くの割合がこのように小さいため、これは参照カウントよりもはるかに高速です。また、バッファの割り当ては、ローカルメモリブロックのコピーに比べて非常にコストがかかります。
Javajava.lang.StringやC# などの他の言語とは異なりSystem.String、C++の文字列は常に可変です。引用符で囲まれたヌル終端文字列定数が、非可変クラスの目的のほとんどを果たしていたためです。C++では、次のコードで文字列のコピーを作成する必要があります。これは、コピーオンライト参照カウントを使用しても、ポインタを渡す場合と比べて非常に低速です。[ 11 ]
import std ;std :: stringを使用します。void outputString ( string s ) { std :: print ( "{}" , s ); }// ... string s = "..." ; outputString ( s ); // s のコピーを作成しますoutputString ( "this is a literal string" ); // 文字列をコピーします(場合によっては2回コピーします)コンパイラはインライン関数に対してこれを最適化できるが、これに依存するものは少なく、ほとんどの場合、文字列は定数参照として渡される。[ 15 ]文字列定数からの変換には一時オブジェクトの構築も必要でstd::string、遅く、通常は関数のオーバーロードにつながる。
import std ;std :: stringを使用します。void outputString ( const string & s ) { std :: print ( "{}" , s ); }void outputString ( const char * s ) { std :: print ( "{}" , s ); }// ... string s = "..." ; outputString ( s ); // s をコピーせず、ポインタ/参照を渡しますoutputString ( "this is a literal string" ); // オーバーロードを呼び出し、生のポインタを渡しますC ++17標準では、読み取り専用データへのポインタと長さのみを持つstd::string_viewクラス[ 16 ]が追加され、不変の一時std::stringオブジェクトの代替としてそのまま使用でき、値渡し引数を上記のどちらの例よりも高速化します。
import std ;using std :: string ; using std :: string_view ;void outputString ( string_view sv ) { std :: print ( "{}" , s ); }// ... string s = "..." ; outputString ( s ); // 間接参照のレベルが 1 つ減りますoutputString ( "これはリテラル文字列です" ); // さらに、コンパイラは strlen() を使用する必要性を最適化で取り除く可能性がありますC++17 では、文字列引数を取るライブラリ内のすべての関数を書き換えて、この利点を活用する必要があります。なぜなら、文字列から文字std::string_view列への変換はstd::string依然としてコストがかかるからです。
文字列ビューは、演算子を使用して文字列リテラルから構築できます。たとえば、[ 17 ]std::literals::string_view_literals::operator""svstring_viewsv="Hello, world!"sv;
std::stringこれは、テンプレート クラスの特定のインスタンス化のためのtypedefです。[ 18 ]その定義は<string>ヘッダーにあります。std::basic_string<CharT, Traits, Alloc>
namespace std { using string = basic_string < char > ; }同様のクラス があり、これはwchar_tstd::wstringで構成され、WindowsではUTF-16テキストを、ほとんどのUnix ライクなプラットフォームではUTF-32テキストを格納するためによく使用されます。ただし、C++ 標準では、これらの型に対してUnicodeコード ポイントまたはコード ユニットとしての解釈を強制せず、が よりも多くのビットを保持することも保証していません。[ 19 ]のプロパティに起因するいくつかの非互換性を解決するために、C++11では 2 つの新しいクラスと(新しい型とで構成され) が追加されました。これらは、すべてのプラットフォームでコード ユニットあたりのビット数が指定されています。[ 20 ] C++11 では、16 ビットおよび 32 ビットの「文字」の新しい文字列リテラルと、Unicode コード ポイントを null 終端 (C スタイル) の文字列に入れるための構文も追加されました。[ 21 ]wchar_tcharwchar_tstd::u16stringstd::u32stringchar16_tchar32_t
A は、それに付随する構造体std::basic_string<CharT, Traits, Alloc>を持つ任意の型に対して特殊化できることが保証されていますstd::char_traits<CharT>。C++11 の時点ではchar、、、wchar_tおよび特殊化char16_tのみchar32_tが実装される必要があります。[ 22 ]ではstd::basic_string<CharT, Traits, Alloc>、2 つのテンプレート パラメータTraitsとには、デフォルトで、およびのAllocデフォルト値があります。std::char_traits<CharT>std::allocator<CharT>
Aは標準ライブラリのコンテナstd::basic_stringでもあるため、文字列内のコード単位に標準ライブラリのアルゴリズムを適用できます。
の設計は、 Herb Sutterstd::stringによってモノリシック設計の例として挙げられており、C++98 のクラスの 103 個のメンバ関数のうち 71 個は実装効率を損なうことなく分離できたはずだと推測している。 [ 23 ]