Contents Up Previous Next

wxString の概要

クラス: wxString, wxArrayString, wxStringTokenizer

イントロダクション
wxString と他の文字列クラスとの比較
wxStringを使用するにあたってのアドバイス
文字列に関する他の関数やクラス
参照カウンタと、それを意識しなくて良い理由
wxString のチューニング


イントロダクション

wxString は任意の長さ (最大は MAX_INT で、32 ビットマシンの場合、通常は 2147483647 です) で任意の文字を含む文字列を表すクラスです。ASCII NUL 文字を含めることも可能ですが、その場合、現在の実装ではいくつかの関数は正しく動作しないことに注意してください。

wxString は ASCII (従来の 7 ビットまたは 8 ビット) 文字列と Unicode (ワイド) 文字列の両方を取り扱うことができます。

このクラスは文字列クラスに含まれていると考えるであろうすべての標準的な文字列操作機能を持っています: 動的なメモリ管理 (新しい文字にあわせて文字列を拡張します)、他の文字列や C 文字列からの構築、代入演算子、各文字へのアクセス、文字列の結合と比較、部分文字列の抽出、大文字小文字の変換、トリムと (スペースによる) パディング、検索と置換、C 言語風の Printf() やストリーム風の挿入関数などです。すべての関数の一覧は wxString を参照してください。


wxString と他の文字列クラスとの比較

C 文字列を直接使用する代わりに特別な文字列クラスを使用する利点は、多くの文字列クラスが存在することから明らかと言えます。もっとも重要な利点は、C 文字列の場合、メモリの割り当てと解放を忘れずに行なう必要がある点です; また、固定サイズのバッファを使用すると大抵バッファオーバーランを引き起こします。最後に、C++ では標準の文字列クラス (std::string) が用意されています。それなのになぜ wxString が必要なのでしょうか?

これにはいくつかの利点があります:

  1. 効率性 このクラスはできるだけ効率的に動作するように作られています: これはサイズ (個々の wxString オブジェクトは 参照カウンタ のおかげで char * ポインタと完全に同じサイズです) と性能面の両方についてです。また、性能の 統計収集コード も提供しており、特定のアプリケーションにあわせてメモリ割り当て戦略をチューニングすることができます。これによる性能向上効果は極めて大きいと思われます。
  2. 互換性 このクラスは wxWidgets 1.xx の wxString クラス、および昔懐かしい MFC の CString クラスとほぼ完全に互換性があり、std::string クラスの機能の 90% と互換性があります。
  3. 豊富な関数群 wxString に存在するいくつかの関数はとても便利ですが、他の文字列クラスの大半には存在しません: 例えば、AfterFirstBeforeLastoperator<<Printf などです。もちろん、標準的な文字列操作にも対応しています。
  4. ユニコード wxString は Unicode との親和性が高いです: どのビルドモードでも簡単に ANSI 文字列や Unicode 文字列と相互に変換できます (詳細は Unicode の概要 を参照してください) し、現在のビルドモードに応じて透過的に stringwstring のいずれかにマッピングされます。
  5. wxWidgets での使用 もちろん、このクラスは wxWidgets 内部のあらゆるところで使用されています。そのため、wxWidgets が内部的に (std::string を含む) 他の文字列クラスを wxString へ変換することによる性能劣化が発生しません。

しかし、同様にいくつかの問題も存在します。もっとも重要な問題はおそらく、まったく同じことを行なう関数が複数存在することです: 例えば、文字列の長さを取得するために length()、Len()Length() のどれでも使用できます。他の小文字の関数のほとんどがそうであるように、最初の関数は std::string と互換性があります。二番目の関数は wxString "ネイティブ" の関数で、最後の関数は wxWidgets 1.xx の形式です。そのため、質問は次のようになります: どの関数を使用すれば良いのか? そして、その答えは次の通りです:

std::string 互換の関数を使用することを強く推奨します! そうすることで、(std::string に関する知識はあっても wxString のことは知らないであろう) 他の C++ プログラマにとってより馴染みのあるコードにできますし、(wxWidgets 外でコードを使用するときは wxString を std::string として typedef することで) wxWidgets とそれ以外のプログラムで同じコードを再利用することもできます。また、wxWidgets の将来のバージョンとの互換性も保てます。なぜなら、遅かれ早かれ、おそらく wxWidgets で std::string を使用し始めるためです。

std::string に対応する関数が存在しない場合は新しい wxString 関数を使用するようにし、wxWidgets 1.xx 版を使用しないようにしてください。これらの関数は非推奨であり、将来のバージョンでは削除されるかもしれません。


wxStringを使用するにあたってのアドバイス

おそらく、このクラスを使用する際の一番の罠は const char * への暗黙の型変換演算子です。変換を行なうタイミングを明確にするために、代わりに c_str() を使用するようにしてください。暗黙の型変換の具体的な危険性は以下のコードで分かると思います:

// この関数は入力された文字列を大文字に変換し、画面に表示した上で
// 結果を返却する
const char *SayHELLO(const wxString& input)
{
    wxString output = input.Upper();

    printf("Hello, %s!\n", output);

    return output;
}
この 3 行の中にふたつの分かりにくいバグが含まれています。最初のバグは printf() 関数の呼び出し部分に存在します。次のような場合であれば、コンパイラによって自動的に C 文字列への暗黙的な変換が行われます。

    puts(output);
なぜなら、puts() の引数が const char * 型であることが分かっているためです。これは (引数が不明な型になる) 可変数引数を受け取る printf() では 行われません 。そのため、この関数呼び出しの結果は (文字列が正しく表示されることも含めて) 不定になりますが、一番起こりうるのはプログラムのクラッシュでしょう。解決方法は c_str() を使用することです: この行を単純に次のように置き換えてください。

    printf("Hello, %s!\n", output.c_str());
ふたつ目のバグは output を正しく返却できないことです。暗黙的な変換が再度行われるため、コードはコンパイルできますが、返却されるポインタはローカル変数のバッファを指すことになります。そして、このローカル変数は関数を抜けるとすぐに破棄されるため、その内容は完全に不定になります。この問題の解決方法も簡単です: C 文字列の代わりに wxString を返却するようにするだけです。

このことから、次のような一般的なアドバイスが可能です: 文字列を引数として受け取るすべての関数は const wxString& を受け取るべきで (参照カウンタ を使用するため、内部文字列への代入が早くなります)、文字列を返却するすべての関数は wxString を返却するべきです。(これにより、安全にローカル変数を返却することができます)


文字列に関する他の関数やクラス

多くのプログラムで文字列が使用されていますが、標準 C ライブラリではそれらのプログラムで使用できる関数をほんの少ししか提供していません。残念なことに、いくつかの関数は直感的でない振る舞いをします (例えば strncpy() は結果の文字列を常に NULL で終端させるとは限りません) し、一般的にあまり安全ではありません。(それらの関数に NULL を渡すとおそらくプログラムがクラッシュするでしょう) その上、いくつかの非常に便利な関数は標準関数ではありません。これが wxString の関数に加えて若干のグローバル文字列関数が存在する理由です: wxIsEmpty() は文字列が空かどうかを確かめます。(NULL ポインタに対しては true を返却します) wxStrlen() も NULL を正しく取り扱うことができ、NULL の場合は 0 を返却します。 wxStricmp() は単なるプラットフォーム非依存の大文字小文字を区別しない文字列比較関数で、プラットフォームによっては stricmp() や strcasecmp() として知られています。

また、<wx/string.h> ヘッダでは wxSnprintf 関数と wxVsnprintf 関数も定義しています。潜在的に危険な標準の sprintf() の代わりにこれらの関数を使用するべきであり、これらの関数ではバッファサイズのチェックを行なう snprintf() をできるだけ使用しています。もちろん、wxString::Printf も安全なので使用しても構いません。

他にも wxString と一緒に使用すると便利なクラスがあります: それが wxStringTokenizer です。このクラスは文字列をトークンに分解する必要があるときに便利で、標準 C ライブラリの strtok() 関数の代わりになります。

そして、文字列に関する最後のクラスが wxArrayString です: これは単なる "テンプレート" 動的配列クラスの一種で、文字列に対して使用するように特殊化されています。このクラスは (wxString の内部構造に関する知識を用いて) 文字列の格納に特化して最適化されています。そのため、wxObjectArray に wxString を格納するより性能面で優れています。


参照カウンタと、それを意識しなくて良い理由

wxString は wxObject を継承していませんが、wxObject を継承した 参照カウント オブジェクトと同様のことが wxString にも当てはまります。

ただ、非 const (もしくは非 const 参照) の文字列から文字を取り出すときだけは参照カウンタを意識する必要があると考えることでしょう。この場合、C++ の規則により、"読み取り専用" の operator[] (これは GetChar() と同じです) は使用できず、代わりに "読み書き可能な" operator[] (これは GetWritableChar() と同じです) が使用されます。この演算子を呼び出すことで文字列が変更されるため、データは共有されなくなります。(COW が行われます) そのため、文字列が本当に共有されていた場合、いくらか性能が (速度とメモリ消費の両方の観点で) 劣化します。このことが重要になるような稀な場合では、添字演算子の代わりに GetChar() を使用した方が良いでしょう。このような状況では at() 関数にも添字演算子と同じ問題があるため、この関数を使用するのは本当に良いとは言えません。また、関数の引数の文字列をすべて const wxString& で受け取っている場合 (アドバイス の章を参照)、この問題がほとんど発生しないことに注意してください。なぜなら、const 参照に対しては自動的に正しい演算子が呼ばれるためです。


wxString のチューニング


注: この章では性能に関する問題のみを取り扱っており、wxString クラスを使用するだけであれば読む必要はまったくありません。プロファイラやその関連ツールについて詳しくないのであればこの章を読み飛ばしてください。もしこの章を読む場合、参照カウンタ の章にも目を通すようにしてください。

性能上の理由から、wxString は各文字列で必要とされる量ちょうどのメモリを割り当てません。代わりに、各割り当て済みブロックに少しだけメモリを追加します。割り当て済みブロックを用いることで、例えば以下のように一度に一文字ずつ連結して文字列を構築する場合などに、頻繁にメモリの再割り当てを行わなくても良くなります:

// 文字列から母音をすべて削除する
wxString DeleteAllVowels(const wxString& original)
{
    wxString result;

    size_t len = original.length();
    for ( size_t n = 0; n < len; n++ )
    {
        if ( strchr("aeuio", tolower(original[n])) == NULL )
            result += original[n];
    }

    return result;
}
これは非常によくある状況であり、余分なメモリを割り当てない場合、著しい性能劣化を引き起こします。これは元の文字列に含まれる文字数分、メモリの (再) 割り当てが行われるためです。この場合では余分なメモリを割り当てることで処理速度が改善されましたが、通常のプログラムでは非常に多くの wxString が使用されるため、メモリ消費量も大きく増加します。

この例での最適な解決方法は Alloc() 関数を用いて、最初に例えば len バイトほど割り当てておくことです。これにより、メモリの割り当てが確実に 1 回だけ行われます。(なぜなら、変換結果の文字列の長さは最大でも元の文字列と同じだからです)

しかし、Alloc() を使用するのは手間がかかるため、wxString ではできるだけのことをしています。デフォルトのアルゴリズムでは少なくとも 16 バイト単位でメモリの割り当てが行われると仮定している (これは広く使われているほぼすべてのプラットフォームで当てはまります) ため、メモリの割り当て量が 16 の倍数に切り上げられたとしても何も無駄になりません。このように、メモリが無駄になることはありませんし、上記の例では 16 回の繰り返しのうち、15 回はメモリの割り当てが行われず、割り当て済みのプールが使用されます。

このデフォルトのやり方は非常に保守的です。より多くのメモリを割り当てることで、(相対的に) とても長い文字列を使用するプログラムでは性能が大きく向上することでしょう。割り当てられるメモリの量は string.cpp ファイルの EXTRA_ALLOC を変更することでコンパイル時に設定することができます。(値を変更する前に、なぜデフォルト値がその値になっているのかをよく理解するようにしてください!) この値を大きく (ここでは nLen の倍にしたとしましょう) したり、(性能の劣化具合を確認するために) 0 に設定したりしてプログラムに与える影響を分析しようとするかもしれません。これを行なう場合、WXSTRING_STATISTICS シンボルも定義すると便利なことにおそらく気がつくでしょう。これを定義すると wxString クラスで性能統計を収集し、プログラムの終了時に標準エラー出力へ出力させることができます。これにより、プログラムで使用する文字列の平均長、平均初期サイズ、および文字列を連結する際にメモリを割り当てず、割り当て済みのメモリを使用した回数の割合 (デフォルトの設定では約 98% のはずですが、これが 90% を下回る場合には wxString のチューニングを本当に検討するべきです) が分かります。

言うまでもないことですが、EXTRA_ALLOC を変更したときの正確な違いを計測するためにプロファイラを使用するべきです。