XPCOM string guide

この記事は Mozilla Japan 翻訳部門または関連グループにより過去に翻訳された文書を移行したものです。 移行元の文書。よって、英語版と内容が異なる場合や、MDN の他の記事との整合性がとれていない場合があります。

序文

by Alec Flett
Thanks to David Baron for actual docs,
Peter Annema for lots of direction,
Myk Melez for some more docs, and
David Bradley for a diagram
Revised by Darin Fisher for Mozilla 1.7
Revised by Jungshik Shin to clarify character encoding issues

 このガイドは過剰に存在しているstringクラスについてドキュメント化したものです。これによって「こういう場合、いったいどのstringクラスを使ったらいいの?」という長年の疑問に対する答えとなることを期待しています。

 もしあなたがMozillaの組み込み開発者か、もしくはMozillaコードベースとは別個に配布されることを予定しているXPCOMコンポーネントを書いているなら、この文書は多くの場合あなたにとって最適のものとは言えません!もしあなたがMozilla 1.7以降を対象に開発を行っているなら、この文書の代わりに新しい最小版APIである Mozilla string API とりわけnsEmbedString クラスを使うべきです。

 お急ぎですか?それなら String Quick-Reference () を見てください。

はじめに

string クラスは、unicode と 1バイト文字の文字列のバッファを扱うために使われる C++ クラスのライブラリです。 これらは xpcom/string ディレクトリの Mozilla コードベースに属します。

Abstract (interface) クラスは"nsA"で始まり、 具象クラスは"ns"で始まります。 "CString" を名前に持つクラスは シングルバイト ASCII 文字列もしくは、UTF-8 やその他の文字セットでエンコードされたマルチバイト unicode 文字列を参照する 8 ビット文字を格納します。 名前に単に "String" を持つ他のすべてのクラスは、 主に UTF16 でエンコードされる 2バイト(PRUnichar)文字列を参照します。 例:nsAString は 2バイト文字を蓄えるための抽象クラスで、 nsDependentCString は 1バイト文字を蓄えるための具象クラスです。 どの 2バイト文字列クラスも同等の 1バイト文字列クラスを持ちます。 例:nsCString クラスは 1バイト文字列クラスで、 nsString と対応します。

1バイトと 2バイトの文字列クラスは完全に異なる基底クラスを持ちますが、 同じ API を共有します。 このように、1バイト文字列を 2バイト文字列へ、何らかのヘルパークラスかルーチンによる変換なしに代入することは出来ません。 このドキュメントの目的は、クラスドキュメントの中で 2バイト文字列クラスについて言及することです。 どの 2バイトクラスも同等の 1バイトクラスを持つと仮定しても安全です。

String ガイドライン

仲間の開発者、レビューワ、ユーザをハッピーにさせるために、コードの中でこれらの単純なルールに従ってください。

  • 是非とも*WithConversion 関数を避けるAssignWithConversion, AppendWithConversion, EqualsWithConversion, など
  • 出来る限りもっとも抽象的なクラスを使う。大抵はこれ:
  • nsAString 互換オブジェクトとしてリテラル文字列(例:"foo")を表現するためには NS_LITERAL_[CSTRING/NS_NAMED_LITERAL_[C]STRING] を使う。
  • 文字列を結合するときにはstring 連結(例: "+" オペレータ)を使う。
  • nsAString互換文字列を変換する必要のある生の文字ポインタを持つとき、 nsDependentStringを使う。
  • 既存の文字列から一部を抜き出すためには Substring() を使う。
  • 文字列断片の解析と抜き出しには iterators を使う。

 

Abstract(抽象)クラス

どの string クラスも nsAString(もしくは nsACString) から派生しています。 このクラスはアクセスと文字列操作のための基礎インタフェースを提供します。 具象クラスが nsAStringから派生する一方、 nsAString自身はインスタンス化できません。

これは、コードベースの他の部分で抽象オブジェクト記述の記述のために Mozilla が使っている "interface" の考え方によく似ています。 インタフェースについて、クラス名は "nsI" で始まり、"I" は "Interface" を意味します。 抽象クラスは "nsA" で始まり、"A" は "Abstract" を意味します。

nsAString から派生した抽象クラスがたくさんあります。 これらの抽象サブクラスもまたインスタンス化できません。 しかし、それらは nsAString よりもわずかながらより詳細に string を記述します。 それからは、抽象クラスの背後で下敷きになった実装が nsAString に加えて特定の能力を提供することを保証します。

以下のリストで主な抽象クラスについて記述します。一度それらになじんだら、どのクラスをいつ使うかを参照してください。

  • nsAString: すべての文字列のための基底クラスです。 これは、代入/個々の文字へのアクセス/基本的なの文字操作/文字列比較のための API を提供します。このクラスは XPIDL の AString 引数型に対応します。
  • nsSubstring: string クラスのすべてに対する共通の基本クラスです。文字列の内部のデータへ最適化されたアクセスをするためのものです。nsSubstring は null 終端文字列である必要はありません(後方互換のために、nsASingleFragmentString は、このクラスを示す typedef がなされています)。
  • nsString: null 終端の保存を保証した nsSubstring によって作られます。このクラスでは、下敷きとなっている文字バッファにアクセスするためのメソッド(.get()) を使うことができます。(後方互換のために、nsAFlatString は、このクラスを示す typedef がなされています)。

その他の string クラスは、nsSubstringnsString かを継承しています。 そのため、どの文字列クラスも nsAString と互換があります。

nsSubstringnsAString は共に null 終端である必要のない一続きの文字の配列を示していることに言及するのは重要なことでしょう。 この似通った二つのクラスが存在する必要があるのは何故なのかと疑問に思う人もいるでしょう。 えぇ、nsSubstring は主として、最適化目的に存在します。それは、nsAString が Mozilla 1.0 とともにリリースされた凍結された nsAString crustバイナリレベルの互換を保たなくてはならないからです。 Mozilla 1.7 のリリースまで、nsAString は複合的な断片に分けられた文字列を表す能力を持っていました。 複合的な断片に分けられた文字列のサポートに関するコストは高く、限られたメリットを提供していました。 文字列クラスの複雑さを減らして、パフォーマンスを改善しようという努力のために、複合的な断片に分けられた文字列のサポートを削減する決定がなされました。詳細は bug 231995 を参照して下さい。

nsSubstring は、下敷きとしている nsAString バッファへのより効果的なインタフェースを提供していますが、nsAString は未だにパラメータ渡しのためにもっとも共通して使われています。 それは、XPIDL の AString と対応した文字辣クラスだからです。 そのために、この文字列ガイドは nsAString に重点を置いた文字列クラスについて言及し続けるでしょう。

どの stiring も nsAString(もしくは nsACString) から派生しているため、それらはいくつかの基礎となる機能を共有します。

読取専用の共通メソッド:

  • .Length() - string 中の文字数。
  • .IsEmpty() - string が何らかの値を持っているかどうか決定する最も早い方法。次のコードでテストするよりこれを使ってください:string.Length == 0
  • .Equals(string) - もし引数の string が現在の string と同じ値を持っていれば TRUE。

文字列修正の共通メソッド:

  • .Assign(string) - 新しい値を string に代入する。
  • .Append(string) - string に値を追加する。
  • .Insert(string, position) - 引数の string を position の文字の後ろに挿入する。
  • .Truncate(length) - string を引数の長さに縮める。

付録に完全なドキュメントがあります。

 

読取専用 strings

string 上でのconst 属性は string が書き込み可能かどうかを示します。 もし、string がconst nsAString のように定義されていたら、string 内のデータは操作不可です。 もし、const でないメソッドを const string 上で呼び出そうとしても、 コンパイラはビルド時にエラーとしてこれを示します。

例:

 

void nsFoo::ReverseCharacters(nsAString& str) {
      ...
     str.Assign(reversedStr); // modifies the string
}

これはコンパイルできない。なぜなら、const クラスへ代入しているから:

void nsFoo::ReverseCharacters(const nsAString& str) {
      ...
     str.Assign(reversedStr);
}

 

関数の引数としての Abstract(抽象)クラス

関数の引数としては、具象クラスの代わりにできる限りもっとも抽象的なインタフェースを使うことが推奨されます。 文字列を参照渡しする場合には、ポインタ('*'文字)ではなく、C++の参照('&' 文字)を用いるのが習慣です。たとえば:

// 抽象クラスによる参照
nsFoo::PrintString(const nsAString& str) {..}

// 具象クラスを使っている!
nsFoo::PrintString(const nsString& str) {..}

// ポインタを使っている!
nsFoo::PrintString(const nsAString* str) {..}

抽象クラスはまた、ときどきオブジェクトへの一時的な参照を蓄えるのにも使われます。 これらの両方の使い方は、後述の共通パターンを参照してください。

 

具象クラス

具象クラスは string データを実際に蓄える必要があるコードの中で使うためのものです。 具象クラスのもっともありがちな使い方は、ローカル変数もしくはクラスや構造体のメンバとして使うというものです。 抽象クラスはそれぞれデータの格納方式が違うために、大抵は具象クラスの格納方針も異なります。

以下はに、もっとも一般的な具象クラスの一覧を示します。一度それらになじんだら、どのクラスをいつ使うかを参照してください。 The following is a list of the most common concrete classes. Once you are familiar with them, see the appendix describing What Class to Use When.

  • nsString / nsCString - バッファがヒープ上に割り当てられている null 終端 string です。 string オブジェクトがなくなったときにそのバッファは破棄されます。
  • nsAutoString / nsCAutoString - nsString から派生した、 string 自身と同じ格納スペースに 64文字バッファを持つ string。 もし、文字長が 64より長い string が nsAutoString に代入されると、新しいバッファがヒープ上に割り当てられます。 これはメンバ変数としてはあまり用いられるべきではないでしょう。
  • nsXPIDLString / nsXPIDLCString- nsString から派生した文字列で、 このクラスは getter_Copies() 演算子によって、XPIDL の出力 wstring / string パラメータへの簡単なアクセスをサポートしています。
  • nsDependentString- nsString から派生した文字列で、 この string は自分自身ではバッファを持ちません。 生の文字列(const PRUnichar*const char*)を nsAString 型に変換するのに有用です。
  • nsPrintfCString- nsString から派生した文字列で、 この string は nsCAutoString のように振る舞います。 このクラスのコンストラクタで printf スタイルのフォーマットの string と引数リストから シングルバイトの string を作ることが出来ます。
  • NS_LITERAL_STRING/NS_NAMED_LITERAL_STRING- これらは("abc" のような)リテラル stringを nsAStrings や nsString のサブクラスに変換します。 プラットフォームでサポートしているダブルバイト string リテラルにおいて(例:MSVC++ や -fshort-wchar オプションを付けた GCC)、これらは nsDependentString クラス周辺の単純なマクロです。 これらは nsDependentString による単なるラップより若干速いです。なぜなら、それらの長さの計算にコンパイラを使い、ダブルバイトリテラル string のごちゃごちゃしたクロスプラットフォーム上の細部を隠しもするからです。

ヘルパールーチンなどの副産物として作られた具象クラスはたくさんあります。 これらのクラスは直接使うことを避けるべきです。 string ライブラリから自分のためのクラスを作ってください。

もちろん、あなたのコードの中のこれらの string クラスの参照が必要な時はあります。 しかし、一般的なルールでは、これらは使わない方がよいです。

イテレータ

イテレータは string の中の位置への参照を保つオブジェクトです。 ある意味で、これらは配列の中のインデックスを参照する数値や、 文字列の中の位置を参照する文字ポインタのようなものです。 イテレータは、文字列への読み込みと書き込みとを区別する文法的な意味も提供します。

イテレータは文字列の部分文字列の抽出のためにもっともよく用いられるべきです。 これらは文字列の内容の修正のための機能を提供しますが、 たいていはヘルパールーチン、つまり文字列自身のメソッドの方が、複雑な文字列変換より早いでしょう。

イテレータは繰り返している文字列クラスで宣言されます:

nsAString::const_iterator start, end; // 読み取り専用イテレータ
    nsAStrings
    nsAFlatString::iterator substr_start, substr_end; // nsString のための書き込み用イテレータ

イテレータは string にある4つのメソッドのうちあなたが参照したい一つにより初期化されます:

// 'str' から読み込もう
str.BeginReading(start); // 'str' の先頭で 'start' を初期化する
str.EndReading(end); // 'end' は string の終端になるでしょう

// 'url' に書き込みもしたいんです
url.BeginWriting(substr_start);
url.EndWriting(substr_end);

ポインタ参照オペレータ * によってイテレータが指す文字へアクセス可能です。

if (*start == '[')
     printf("Starts with a bracket\n");

上記例では、'end' と 'substr_end' は実際にかつて string の終端だった文字を指すでしょう。 なので、.EndReading() の結果を直接ポインタ内容参照してはいけないことに注意してください。

二つのイテレータが同じ位置を指すかどうかは、== か != で調べることが出来ます。 ++ でイテレータの参照を進めることも出来ます。 ++ はイテレータの前にもってくることが好まれます。そして、それは一時的なイテレータの作成を防ぐことになります。

 

while (start != end) // string 全体を通して順番に回る
     ++start;

(const-iterators とは反対のように)書き込みイテレータにより、効果的に string に書き込むことが出来ます:

 

// * をすべて ! に変える
while (substr_start != substr_end) {
     if (*substr_start == '*')
          *substr_start = '!';
     ++substr_start;
}

With the patch for bug 231995, this loop is now as efficient as iterating with raw character pointers.

 

イテレータによるループ

原文ではindex部分には残っていますが、見出し、a nameを含めて削除されています。

ヘルパークラスと関数

文字列検索

FindInReadable() はかつての string.Find(..) の代わりのものです。 構文は:

PRBool FindInReadable(const nsAString& pattern,
                      nsAString::const_iterator start, nsAString::const_iterator end,
                      nsStringComparator& aComparator = nsDefaultStringComparator());

これを使うために、startend は検索したい文字列のそれぞれ先頭と終端をさしていなくてはなりません。 もし、探している文字列が見つかったら、startend は見つかった部分の先頭と終端を指すように調整されます。 戻り値は、PR_TRUE か PR_FALSE で、文字列が見つかったかどうかを示します。

例:

const nsAString& str = GetSomeString();
nsAString::const_iterator start, end;

str.BeginReading(start);
str.EndReading(end);

NS_NAMED_LITERAL_STRING(valuePrefix, "value=");

if (FindInReadable(valuePrefix, start, end)) {
    // end は今、検索した文字の後ろを指している
    valueStart = end;
}

メモリ割当

既存の文字列から新しい文字列バッファ(PRUnichar*/char*)を割り当てるために好ましいメソッドは、 以下のメソッドです:

  • PRUnichar* ToNewUnicode(nsAString&) - nsAString から PRUnichar* を割り当てます。
  • char *ToNewCString(nsACString&) - nsACString から char* バッファを割り当てます。 このメソッドは nsAStrings 上でも働きますが、暗黙の損失の多い変換となるでしょう。 この機能は入力が厳密に ASCII であることが判っている場合にだけ使うべきです。しばしば UTF8 への変換がより適しています。 次項 ToNewUTF8String も参照のこと。
  • char* ToNewUTF8String(nsAString&) - 与えられた nsAString の UTF8 エンコードされたバージョンを含む新しい char* バッファを割り当てます。 詳細はUnicode 変換を参照して下さい。

これらのメソッドは These methods return a buffer allocated using XPCOM's allocator (nsMemory::Alloc) instead of the traditional allocator (malloc, etc.). You should use nsMemory::Free to deallocate the result when you no longer need it. これらのメソッドは伝統的なアロケータ (malloc など)の代わりに XPCOM のアロケータ (nsMemory::Alloc)で割り当てられたバッファを返すでしょう。 必要としなくなった時、その戻り値を開放するために nsMemory::Free を使うべきです。

既存の文字列の断片

実際に新しいスペースを割り当てたり、その文字列の部分文字列の文字をコピーしたりしないで既存の文字列の部分文字列を参照するのはとても簡単です。Substring() はそのような文字列への参照を生成するのにとてもよいメソッドです。

void ProcessString(const nsAString& str) {
    const nsAString& firstFive = Substring(str, 0, 5);
    // firstFive は今最初の 5 文字を示す文字列です
}

 

Unicode 変換

文字列は二つの基本的な形式で保存することができます。 8 ビット文字(char)文字列もしくは、16 ビット文字(PRUnichar)文字列です。 クラス名に大文字の「C」を持つどの文字列クラスも、8 ビット文字を含みます。 それには、nsCString、nsDependentCString などのクラスが含まれます。 「C」を持たないどの文字列クラスも、16 ビット文字を含みます。

格納構造に加え、文字列はまた、エンコードもされているという側面があります。 文字列のエンコーディングは、8 もしくは 16 ビットの文字列として unicode 文字値の組を保持することを意味します。 大きな文字値を小さな容量の文字列として保存する方法がたくさんあるため、エンコーディングがたくさんあります。 文字セットは、特定のエンコーディング方法の人間が理解できる名称です。 例えば、「ASCII」は 7 ビット値を 8 ビット文字列にマッピングした文字セットです。 「isolatin1」は8 ビット文字列で西欧文字エンコーディングするための共通文字セットです。

文字列の文字セットは、文字列クラスの名前によって定義されていません。 そのかわり、文字列の適切なエンコーディングを決定することは実装者の責任です。 もっとも一般的なのエンコーディングは:

  • ASCII - 基本的な英語のみの文字列のための 8 ビットエンコーディングです。 どの ASCII 値も配列の正確に 1 バイトに格納されます。
  • UCS2 - 基本の unicode 格納のためのエンコーディングです。 UCS2 で格納された文字の unicode 値は、文字列クラスの正確に一つの 16 ビット PRUnichar に格納されます。
  • UTF8 - unicode 値のための8 ビットエンコーディング。 どの UTF8 値も 1 から 6 バイトの組で保持されます。 UTF8 は unicode 文字セット全体を表現する能力があり、効率よく UTF32 へマップします。
  • UTF16 - 拡張 unicode 格納のための 16 ビットエンコーディングです。 UCS2 に対して後方互換性があります。 UTF16で格納された unicode 文字値は、文字列クラスの 1 つもしくは 2 つの 16ビット PRUnichar を必要とします。 このエンコーディングは現在頻繁には使われません。しかし、より新しい unicode 標準が採用されるにつれて、きっと増えるでしょう。 UTF16 は unicode 文字セット全体を表現する能力があり、効率よく UTF32 へマップします。

加えて、国際化ライブラリによって提供される文字通り何百のエンコーディングがあります。 これらのライブラリへのアクセスは、アプリケーションの一部であるか(例えば Mozilla の nsICharsetConversionManager のように)、オペレーティングシステムへビルドされている(例えば Unix 系 OS の iconv() のように)でしょう。

既存のコードで作業する時、正しい変換機構を決定するために、操作している文字列の現在の使い方を吟味することは重要です。

新しいコードを書く時、どの格納クラスそしてエンコーディングがもっとも適切かを知ろうとしても混乱するかもしれません。この問題への単純な答えはありません。しかし、少数の重要なガイドラインがあります:

  • 文字列はいつも ASCII ? 最初の、そして最大のもので、どの種類の値が文字列に格納されているかを決めることが必要です。文字列がいつでも内部の ASCII 文字列、例えば「left」、「true」、「background」などであれば、そのままの C-文字列がたぶん選ぶべきものです。
  • 文字列が ASCII であれば、ASCII でない文字列と比較・代入・さもなければ相互作用しますか? 8 ビット ASCII 値を 16 ビット UCS2 文字列と代入もしくは比較するとき、実行時に「膨らませる」必要があります。 もし、文字列が十分に小さい(そう、 64 バイトより小さい)ならば、余計な変換を避けるために 16 ビット unicode クラスにも文字列を格納する、というのも手です。そのかわり、ASCII 文字列が、8 ビット文字列だった場合に比べて 2 倍のスペース、つまり 16 ビット unicode 文字列と同じスペースを占める、という欠点があります。
  • 文字列はたいてい ASCII であるけれど、unicode をサポートしなくていいのでしょうか? 文字列はほとんど大抵 ASCII であるけれど、unicode 値も格納する必要があるのなら、UTF8 は正しいエンコーディングだ。 ASCII 値は 8 ビットのまま格納され、拡張 unicode 値は 2 〜 6 バイトで格納されるでしょう。 しかし、もし文字列が unicode 値との比較や代入までも必要とするのなら、実行時変換が必要となるでしょう。
  • ASCII でない大きな文字列データを格納しますか? ここに至るまでは、 UTF8 は理想的な文字列に見えます。 欠点は、西欧以外の文字のほとんど(例えば日本語文字)を使う場合に、UTF8 は「ふくれ気味な」エンコーディングです。 UTF-8 では、日本語文字列は大抵一文字あたり 3 バイト必要です。 UCS2 では、一文字が 2 バイトであるのと比べて、日本語テキストにおいて UTF-8 は相当に多くのサイズを消費します。
  • unicode 文字列のコンテントを処理する必要がありますか? UTF8 やその他の 8 ビット格納形式で unicode 値をエンコーディングすることの一つの問題点は、実際も unicode 値も文字列の中で複数バイトにわたることです。 ほとんどのエンコーディングでは、実際のバイト数は文字から文字へで異なります。 それぞれの文字を通して反復処理する必要があるときには、エンコーディングを考慮する必要があります。 UCS2 文字列で反復処理するなら、これは非常に簡易です。なぜなら、どの 16 ビット PRUnichar も、unicode 値と対応するからです。

ASCII、UTF8、UCS2 の変換を助けるため、 いくつかのヘルパーメソッドとヘルパークラスがあります。 これらのクラスのうちいくつかは、スタック上の一時オブジェクトとしてもっともよく使われるため、関数のように見えます。

To assist with ASCII, UT8 and UCS2 conversions, there are some helper methods and classes. Some of these classes look like functions, becuase they are most often used as temporary objects on the stack.

UTF8 / UCS2 変換

  • NS_ConvertUTF8toUCS2(const nsACString&) - UTF-8 エンコードされた nsACString もしくは const char* を UCS2 string に変換する nsAutoString のサブクラス。もし、代わりに const PRUnichar* バッファが必要なら、.get() メソッドを使ってください。例:
/* シグネチャ: void HandleUnicodeString(const nsAString& str); */
object->HandleUnicodeString(NS_ConvertUTF8toUCS2(utf8String));
/* シグネチャ: void HandleUnicodeBuffer(const PRUnichar* str); */
object->HandleUnicodeBuffer(NS_ConvertUTF8toUCS2(utf8String).get())
         
  • NS_ConvertUCS2toUTF8(const nsAString&) - UTF8 エンコードされた nsAString を UTF8 された文字列に変換する nsAFlatCString。上記同様に、const char* へアクセスするときは、.get() を使ってください。
  • NS_ConvertUCS2toUTF8(const nsAString&) - UCS2 エンコードされた nsAString を UTF-8 エンコードされた string へ変換する nsCAutoString。 上記項目と同様に、const char* にアクセスするために .get() を使うことが出来ます。
/* シグネチャ: void HandleUTF8String(const nsACString& str); */
object->HandleUTF8String(NS_ConvertUCS2toUTF8(unicodeString));
/* シグネチャ: void HandleUTF8Buffer(const char* str); */
object->HandleUTF8Buffer(NS_ConvertUCS2toUTF8(unicodeString).get())
  • CopyUTF8toUCS2(const nsACString&, const nsAString&) - 変換と割り当てを行う。
// UCS2 値を返す
void Foo::GetUnicodeValue(nsAString& result) {
    CopyUTF8toUCS2(mLocalUTF8Value, result);
}
  • CopyUCS2toUTF8(const nsAString&, const nsACString&) - 変換と割り当てを行う。
// UTF8 値を返す
void Foo::GetUTF8Value(nsACString& result) {
    CopyUCS2toUTF8(mLocalUnicodeValue, result);
}
  • ToNewUTF8String(const nsAString&) - 割り当てと変換を行う
void Foo::GetUTF8Value(const char** result) {
  *result = ToNewUTF8String(mLocalUnicodeValue);
}

 

損失の多い変換

以下はオリジナル文字列が ASCII ベースであることが保証できるときだけ使われるべきです。

UCS2 から ASCII へのコンバータ

これらのコンバータ(変換機構)は、変換プロセスの中で情報の消失があるためとても危険です。UCS2 から ASCII への変換は、文字列が ASCII であることが保証されない限りさけるべきです。どの UCS2(16 ビット)文字も、8 ビット文字は単に 8 ビット文字にキャストされます。それは、0xFF を超えるすべての文字の値は任意の 8 ビット文字に変換されてしまうということです。

  • NS_LossyConvertUCS2toASCII(nsAString) - string の圧縮した値を含む一時バッファを持つ nsCAutoString。
  • CopyUCS2toASCII(nsAString, nsACString) - UCS2 から ASCII 文字列オブジェクトへコピーの変換をします。
  • ToNewCString(nsAString) - 新しい char* 文字列を割り当てます。

 

ASCII から UCS2 へのコンバータ

これらは、ASCII でない文字列を無意味な unicode 文字列に壊してしまうため、とても危険です。 ASCII から UCS2 への変換は、文字列が ASCII であることが保証されない限りさけるべきです。 つまり、もし複数バイト文字セットの 8 ビット文字列エンコードされたものを持っている場合、文字列のどのバイトもただキャストによって 16 バイト数値に"水増し"されるだけなのです。

例えば、文字列の最初の unicode 文字が 4 バイトの UTF-8 シーケンスで表されているような UTF-8 文字列を想像してください。"水増しされた" unicode 文字列の最初の 4 バイトは最初の文字をあらわす 4 つの値を含んでいます。これらの値は文字列を UCS2 として扱うなら無意味です。

  • NS_ConvertASCIItoUCS2(nsACString) - string の水増しした値を含む一時バッファを持つ nsCAutoString。
  • NS_ConvertASCIItoUCS2(nsACString) - 水増しした文字列の値を含む一時バッファをもつ nsAFlatString。
  • CopyASCIItoUCS2(nsACString, nsAString) - ある文字列から unicode 文字列オブジェクトへコピーの変換をします。
  • ToNewUnicode(nsACString) - 水増しした値を含む新しい PRUnichar* 文字列を生成します。

 

共通パターン

多くの API は、呼び出し元に文字列を返すためのバッファを割り当てるメソッドとなります。 バッファを使い終えた時に呼び出し元が文字列を解放することを覚えている必要があるため、これはトリッキーでもあります。 幸運にも、nsXPIDLString クラスでこれを簡単に行えます。


メソッドはこのような感じです:

void GetValue(PRUnichar** aValue)
{
    *aValue = ToNewUnicode(foo);
}

文字列クラスなしで、呼び出し元は文字列を解放する必要があるでしょう:

{
    PRUnichar* val;
    GetValue(&val);

    if (someCondition) {
        // 値を解放することを忘れてはいけない
        // don't forget to free the value!
        nsMemory::Free(val);
        return NS_ERROR_FAILURE;
    }

    ...
    // 後で、やはり解放を忘れてはいけない!
    // and later, still don't forget to free!
    nsMemory::Free(val);
}

nsXPIDLString を使えば、このことを心配する必要はありません。 getter_Copies() を使って文字列クラスをラップするだけで、 スコープの外へ出た時にクラスがバッファを解放するでしょう。

{
    nsXPIDLString val;
    GetValue(getter_Copies(val));
    // val はここで自身によって解放されるでしょう
    // val will free itself here
    if (someCondition)
        return NS_ERROR_FAILURE;
    ...
    // 後で、やはり解放する必要がない
    // and later, still nothing to free
}

結果としてのコードはとても単純で、読みやすいです。

 

リテラル文字列

リテラル文字列は C++ コードに書かれた生の文字列の値です。 例えば、printf("Hello World\n"); ステートメント中の値 "Hello World\n" はリテラル文字列です。 nsAString や nsACString が必要なとき、リテラル文字列値を挿入する必要がしばしば発生します。 これら 4 つのマクロは必要な変換のために提供されています:

  • NS_LITERAL_CSTRING(literal string) - 一時的 nsCString
  • NS_NAMED_LITERAL_CSTRING(variable,literal string); - variable と名づけられた nsCString 変数を定義します
  • NS_LITERAL_STRING(literal string) - Unicode 版literal string(リテラル文字列)を持つ一時 nsString
  • NS_NAMED_LITERAL_STRING(variable,literal string); - Unicode 版 literal string(リテラル文字列)を持つ、名称 variable の nsString 変数を宣言する

nsDependentCString もまた nsCString の中の文字列の値をラップすることを考えれば、これらのマクロの CSTRING 版は、一見不要に見えます。これらのマクロの長所は、これらの文字列の長さがコンパイル時に計算できるため、実行時に長さを決めるために文字列を読み込む必要がありません。

これらのマクロの STRING 版は、(例えば、MSVC++ や -fshort-wchar オプション付きの GCC のように)リテラル unicode 文字列をサポートするプラットフォーム上での実行時の変換をしないで、unicode 版の固定のリテラル string を宣言する移植性のある方法を提供します。

// Init(const PRUnichar*) 呼び出し
Init(L"start value"); // よくない - L"..." は移植性が低い!
Init(NS_ConvertASCIItoUCS2("start value").get()); // よくない - 実行時の ASCII->UCS2 変換!

// Init(const nsAString&) 呼び出し
Init(nsDependentString(L"start value")); // よくない - 移植性が低い!
Init(NS_ConvertASCIItoUCS2("start value")); // よくない - 実行時の ASCII->UCS2 変換!

// Init(const nsACString&) 呼び出し
Init(nsDependentCString("start value")); // よくない - 文字列長が実行時に決まる

適切な NS_LITERAL_[C]STRING 使用法を以下にいくつか示します。

// Init(const PRUnichar*) 呼び出し
Init(NS_LITERAL_STRING("start value").get());

// Init(const nsAString&) 呼び出し
Init(NS_LITERAL_STRING("start value"));

// Init(const nsACString&) 呼び出し
Init(NS_LITERAL_CSTRING("start value"));


これらのマクロを使った問題の追跡に役に立つであろう詳細をいくつか示します:

NS_LITERAL_STRING は(Windows や Macintosh などといった)いくつかのプラットフォーム上でコンパイル時に UCS2 への変換をしますが、他のプラットフォームでは実行時に変換されます。NS_LITERAL_STRING を使うことによって、あなたのコードで問題のプラットフォームのために最良の変換が使われることを保証します。

いくつかのプラットフォームで実行時変換が行われるため、NS_LITERAL_STRING/NS_NAMED_LITERAL_STRING マクロ内部でのリテラル文字列連結の使用は、それらのプラットフォームではコンパイルされるでしょうが、コンパイル時変換をサポートするプラットフォーム上ではコンパイルできないでしょう。

以下に例示します:

 

// Init(nsAString&) 呼び出し
Init(NS_LITERAL_STRING("start "
     "value")); // いくつかのプラットフォームでだけコンパイルされます。

その理由は、いくつかのプラットフォームでL"..." 構文が使われますが、これは連結の最初の文字列にだけ適用されるためです("start ")。コンパイラは unicode でない文字列 "value" との連結を試みると困惑します。

文字列連結

文字列は + 演算子を用いて互いに連結可能です。結果となる文字列は const nsAString オブジェクトとなります。 結果となる文字列は、その他の nsAString のように振る舞いをさせることも参照もできます。 連結は部分文字列のコピーではありません。代わりに、オリジナルの文字列を参照するに過ぎません。 結果としての文字列は、少なくとも連結された文字列と同じだけの生存期間を持つという意味で、その部分文字列のすべてに依存します。

例えば、二つの文字列の値を使うことができ、接続を const nsAString& をとる他の関数に引き渡すことができます:

void HandleTwoStrings(const nsAString& one, const nsAString& two) {
    // HandleString(const nsAString&) 呼び出し
    HandleString(one + two);
}

注意:このケースでは、二つの文字列は暗黙のうちに一時的な nsString に結びつけられ、 一時的 string は、HandleString に渡されます。 もし、HandleString がその入力を他の nsString へ代入したならば、 いくつかの文字列を連結し、その結果を一時変数に格納することもできます:

NS_NAMED_LITERAL_STRING(start, "start ");
NS_NAMED_LITERAL_STRING(middle, "middle ");
NS_NAMED_LITERAL_STRING(end, "end");
// 3つの相互依存した断片を持つ string を生成 - 複雑なコピーではない!
nsString combinedString = start + middle + end;

 

// void HandleString(const nsAString&); 呼び出し
HandleString(combinedString);

もし、一度きりだけ使う一時的なものを作るために NS_LITERAL_STRING を使うのなら、 結合の中で定義するのが安全でしょう。なぜなら、文字列バッファは(nsSubstringTuple 型の)一時的結合オブジェクトと同じ生存期間となるからです。

// HandlePage(const nsAString&); 呼び出し
// 結合された文字列はその部分文字列と同じ生存期間なので安全
HandlePage(NS_LITERAL_STRING("start ") + NS_LITERAL_STRING("end"));

ローカル変数

関数内のローカル変数は通常、スタック上に確保されます。 nsAutoString/nsCAutoString クラスは、 nsString/nsCString の派生物です。 これらが、文字列自身と同じ格納領域に割り当てられた 64 文字のバッファを持ちます。 もし、nsAutoString がスタック上に割り当てられていれば、文字列は破棄時に 64 文字スタックバッファ持ちます。 They own a 64-character buffer allocated in the same storage space as the string itself. If the nsAutoString is allocated on the stack, then it has at its disposal a 64-character stack buffer. このことにより、小さな文字列を扱う時、余計なメモリの割り当てをしないで実装することができます。

...
nsAutoString value;
GetValue(value); // 結果が 64 文字より少なければ、
                 // 割り当てを省くことができる。
GetValue(value); // if the result is less than 64 characters,
                 // then this just saved us an allocation
...

メンバ変数

一般に、メンバ変数としては、nsStringnsCString といった具象クラスを使うべきでしょう。

class Foo {
    ...
    // これらは UTF8 や unicode 値を各々格納する
    // these store UTF8 and Unicode values respectively
    nsCString mLocalName;
    nsString mTitle;
};

文字列は直接クラスの中で宣言され、文字列へのポインタとして宣言されるのではないことに注意してください。 このようにはしないでください:

Note that the strings are declared directly in the class, not as pointers to strings. Don't do this:

class Foo {
public:
    Foo() { mLocalName = new nsCString(); }
    ~Foo() { delete mLocalName; }

private:
    // これらは UTF8 や unicode 値を各々格納する
    // these store UTF8 and Unicode values respectively
    nsCString* mLocalName;
};

上記のコードは文字列オブジェクトのコストを節約しているように見えるかもしれませんが、 nsString/nsCString は小さなオブジェクトです。 割り当てのオーバーヘッドは、ポインタで節約するよりも数バイト勝っているだけです。


その他の間違ったパターンとしては、メンバ変数として、 nsAutoString/nsCAutoString を使うというものがあります。 ローカル変数で述べたように、 これらのクラスはとても大きなバッファを中にもって作られます。 もしクラスの中にこれらを持つことは、クラスを 64 バイト(nsCAutoString)もしくは 128 バイト(nsAutoString)膨らませることになります。


例:

class Foo {
    ...

    // Foo クラスが 128 バイト膨らむ!
    // bloats 'Foo' by 128 bytes!
    nsAutoString mLocalName;
};

 

生の文字ポインタ

PromiseFlatString() は、元になった文字列と同じ値を含む null 終端のバッファを持つ一時バッファを生成するのに使うことができます。 PromiseFlatString() は必要ならば一時バッファを作ります。 これは多くの場合、nsAString を null 終端文字列を要求する API に通すために使われます。

以下の例では、nsAString はリテラル文字列と一体化されます。そして結果は単純な文字バッファを求める API に通されます。

// URL を修正し、AddPage(const PRUnichar* url) へ通す
void AddModifiedPage(const nsAString& url) {
    NS_NAMED_LITERAL_STRING(httpPrefix, "http://");
    const nsAString& modifiedURL = httpPrefix + url;

    // 一時バッファ生成
    AddPage(PromiseFlatString(modifiedURL).get());
}

既に null 終端である文字列を扱うとき、PromiseFlatString() は洗練された方法です。 そのようなケースで一時バッファを作らなくてすみます。

 

// URL を修正し、AddPage(const PRUnichar* url) へ通す
void AddModifiedPage(const nsAString& url, PRBool addPrefix) {
    if (addPrefix) {
        // 一時バッファを作成なければならない - 文字列は複数の断片でできている
        NS_NAMED_LITERAL_STRING(httpPrefix, "http://");
        AddPage(PromiseFlatString(httpPrefix + modifiedURL));
    } else {
        // 一時バッファを作成してもよい、実行時にチェックする
        AddPage(PromiseFlatString(url).get());
    }
}

 

IDL

文字列ライブラリは IDL を通しても利用できます。 特別に IDL 型を定義することによって属性やメソッドを宣言することで、 string クラスは対応メソッドへの引数として使えます。

 

IDL 文字列型

C++ シグネチャにより、メソッドのすべての引数が抽象クラスをベースにしているようなのような上述通りの抽象型を定義することができます。 以下のテーブルには IDL のそれぞれの文字列型の目的を記します。

IDL type C++ Type Purpose
string char* Raw character pointer to ASCII (7-bit) string, no string classes used. High bit is not guaranteed across XPConnect boundaries
wstring PRUnichar* Raw character pointer to UTF-16 string, no string classes used
AString nsAString UTF-16 string
ACString nsACString 8-bit string, all bits are preserved across XPConnect boundaries
AUTF8String nsACString UTF-8 string - converted to UTF-16 as necessary when value is used across XPConnect boundaries
DOMString nsAString UTF-16 string used in the DOM. More or less the same as AString, but in JavaScript it has no distinction between whether the string is void or just empty. (not sure on this, looking for corrections.

C++ シグネチャ

IDL では、in 引数は読み込み専用で、*String 引数に対する C++ シグネチャにより、それらの引数のための const nsAString& を使った上記ガイドラインが行えます。 outinout 引数は、呼ばれた側で書き込み可能なよう単純に nsAString として定義されています。

IDL C++
interface nsIFoo : nsISupports {

    attribute AString utf16String;



 
    AUTF8String getValue(in ACString key);

};
class nsIFoo : public nsISupports {

     NS_IMETHOD GetUtf16String(nsAString&
                               aResult) = 0;
     NS_IMETHOD SetUtf16String(const nsAString&
                              aValue) = 0;

     NS_IMETHOD GetValue(const nsACString& aKey,
                     nsACString& aResult) = 0;
};

上記の例では、unicodeString は Unicode 文字列として扱われています。 GetUnicodeString() のインプリメントでは aResult.Assign を値を返す("return"する)のに使っています。 SetUnicodeString() では、文字列の値はイテレータPromiseFlatString、その他の文字列への代入などを含む雑多なメソッドを通して使うことができます。

GetValue() では、最初の引数である aKey は、生の 8 ビット 値の連続として扱われます。 aKey 内の ASCII でないどの文字列も、XPConnect 境界を越えた場合も内容が保証されます。 GetValue() のインプリメンテーションは UTF-8 エンコードされた 8 ビット文字列の aResult への代入となっています。 もし、このメソッドがスクリプトからの呼び出しなどによって XPConnect の境界を越えて呼ばれたとき、結果は UTF-8 から UCS2へデコードされ、Unicode 値として利用されます。

文字列型の選択

IDL で使う正しい文字列型を決めるのは難しいかもしれません。 以下の点は適切な文字列型を決める助けになるでしょう。

  • 文字列クラスを用いることは、out 引数へ新規にメモリ割当をすることを防ぐでしょう。 例えば、もし呼び出し側が nsAutoStringout 引数のための値を受け取るのに使っている場合、(C++ 内で単純に nsAString として定義されている)短い(64 文字以下の)値の out 引数への代入は nsAutoString のバッファへの値のコピーに過ぎません。 それ以上に、文字列クラスを使うことで、文字列バッファを共有することができます。 多くの場合、ある文字列オブジェクトから別の文字列オブジェクトへ代入することにより、参照のカウントを単に増やすことを優先してコピーを避けることが出来ます。
  • 文字列クラスを使った in 文字列は、しばしば長さを事前に計算します。これはパフォーマンス上のメリットとなるでしょう。
  • 生の文字バッファが必要とされる場所では、stringwstringPromiseFlatString よりも高速なアクセスを提供します。
  • AUTF8String で定義された UTF-8 文字列は、XPConnect 境界を越えるとき、デコードされる必要があるでしょう。 これはパフォーマンスに打撃を与えます。一方で、 UTF-8 文字列は共通で用いられる ASCII 文字列では省スペースしか占有しません。
  • wstringAString で定義された Unicode 文字列は、Unicode 値が必要とされるときは、速いです。 しかし、もし値によりしばしば ASCII が使われるなら、下敷きとなった文字列の格納スペースの半分は無駄になります。

付録 A: どのクラスをいつ使うか

この表はどのクラスをいつ使うべきかを示すクィックリファレンスです。

内容 クラス メモ
ローカル変数 nsAutoStringnsCAutoString  
クラスのメンバ変数 nsStringnsCString  
メソッドの引数の型 nsAStringnsACString 引数に抽象クラスを使う。入力引数には const nsAString& を使い、出力引数には nsAString& を使う。
出力文字列を回収するRetrieving "out" string/wstrings nsXPIDLStringnsXPIDLCString getter_Copies() を使う。nsString / nsCString と似ている。
文字バッファをラップするbuffers nsDependentStringnsDependentCString const char* / const PRUnichar* バッファをラップする。
リテラル文字列 NS_LITERAL_STRINGNS_LITERAL_CSTRING nsDependent[C]String と似ているが、ビルド時に長さが事前計算される。

付録 B: nsAString リファレンス=

読み込み専用メソッド

  • Length()
  • IsEmpty()
  • IsVoid()
  • BeginReading(iterator)
  • EndReading(iterator)
  • Equals(string[, comparator])
  • First()
  • Last()
  • CountChar()
  • Left(outstring, length)
  • Mid(outstring, position, length)
  • Right(outstring, length)
  • FindChar(character)

文字列を修正するメソッド

  • Assign(string)
  • Append(string)
  • Insert(string)
  • Cut(start, length)
  • Replace(start, length, string)
  • Truncate(length)
  • SetIsVoid(state)
  • BeginWriting(iterator)
  • EndWriting(iterator)
  • SetCapacity()

Document Tags and Contributors

タグ:
Contributors to this page: Debuyo, Potappo, Taken, ethertank, Mgjbot
最終更新者: ethertank,