## 3.3 C 言語でのロケールと C++ ロケールとの違い

これまで述べてきたように、C 言語でのロケールと C++ ロケールでは同様のサービスを利用することができます。ただし、意味論の点では、C++ ロケールと C 言語でのロケールには違いがあります。

- 標準の C 言語でのロケールは大域リソースです。アプリケーション全体でロケールは 1 つだけです。そのため、複数のロケールを同時に処理するアプリケーションを構築するのが困難です。
- 標準 C++ ロケールはクラスです。 さまざまな locale クラスのインスタンスを任意に作成できるため、必要なロケールオブジェクトだけを用意することができます。

さらに詳しくこの違いを調べるために、ロケールの一般的な使用方法を次に説明します。

### 3.3.1 C 言語でのロケールの一般的な使用方法

C 言語でのロケールは、一般的にデフォルトのロケール、ネイティブロケール、マルチロケールアプリケーションとして使用します。

デフォルトのロケール。開発を行う際に国際化機能は不要です。したがって、 `setlocale()` を呼び出す必要はありません。アプリケーションのユーザーが、代表的なアメリカ英語の ASCII 動作を使用しているのが確実であれば、地域対応化の必要はありません。それ以外の場合でも、デフォルトのロケールであるアメリカ英語の ASCII ロケールは、常に使用することになるでしょう。

ネイティブロケール。プログラムの地域対応化を予定している場合は、プログラムの最初にネイティブロケールを呼び出したら、決して変更しないのが最適です。この方法では、アプリケーションを特定のロケールに適合させ、実行時に一貫してそれを使用します。このようなアプリケーションでは、アプリケーションの開始前に、任意のロケールをユーザーが明示的に設定することができます。 この場合、UNIX システムでは、`LANG` などの環境変数を設定して対応します。その他のオペレーティングシステムでは、他の方法で対応します。

プログラムでは起動時に `setlocale("")` を呼び出して、空の文字列をロケール名として渡すと、任意のネイティブロケールを指定することができます。 空の文字列を指定すると、ユーザーが環境内で指定したロケールが `setlocale` に使用されます。

マルチロケール。マルチロケールの処理はめずらしいことではありません。たとえば、スイス向けのアプリケーションの実装では、イタリア語、フランス語、ドイツ語の出力メッセージが必要になるでしょう。C 言語でのロケールは大域データ構造なので、何度かロケールを切り替える必要があります。

マルチロケールによるアプリケーションの例として、世界各地の顧客に請求書を発送するアプリケーションがあると想定します。もちろん、請求書は顧客の国の言語で印刷されます。したがって、アプリケーションによる出力も複数の言語で出力されます。請求書の金額は、1 つの価格表が元になっています。このアプリケーションがアメリカ合衆国の企業で使用される場合、価格表はアメリカ英語で作成します。

アプリケーションでは、アメリカ英語で入力 (製品の価格表) を読み取り、出力 (請求書) は顧客の母国言語、たとえばドイツ語で書かれます。C では、入力と出力の両方に対応する大域ロケールが 1 つしかないため、大域ロケールは入力動作と出力動作の間で切り替えが必要になります。英語の価格表から価格を読み出すには、請求書の印刷に使用したドイツ語からアメリカ英語に切り替える必要があります。請求書に価格を印刷するには、大域ロケールをドイツ語ロケールに戻す必要があります。価格表から次の入力を読み取るときは、再び英語に切り替えます。以下同様にして作業を進めます。図 6 は、この動作をまとめたものです。

#### 図 6 -- C のマルチロケール

以下に示すのは、先の例に対応する C のプログラムです。

>     double price;
>     char buf[SZ];
>     while (  )  // ドイツ語請求書の処理
>     {  setlocale(LC_ALL, "En_US");
>        fscanf(priceFile,"%lf",&price);
>        // 現在の通貨交換レートにしたがって、$ を DM に変換する
>        setlocale(LC_ALL,"De_DE");
>        strfmon(buf,SZ,"%n",price);
>        fprintf(invoiceFile,"%s",buf);
>     }

C++ ロケールオブジェクトでは、複数のロケール間の通信タスクがめざましく簡略化されます。ストリームに別々のロケールオブジェクトを組み込むことができるように、標準 C++ ライブラリの入出力ストリームは国際化されています。たとえば、入力ストリームに英語ロケールオブジェクトを組み込み、出力ストリームにはドイツ語ロケールオブジェクトを組み込むといったことができます。こうして、図 7に示すように、ロケールの切り替えが不要になります。

#### 図 7 -- C++ のマルチロケール

以下に示すのは、先の例に対応する C++ のプログラムです。1

>     priceFile.imbue(locale("En_US"));
>     invoiceFile.imbue(locale("De_DE"));
>     moneytype price;
>     while (  )  // ドイツ語請求書の処理
>     {  priceFile >> price;
>        // 現在の通貨交換レートに従って、$ を DM に変換する
>        invoiceFile << price;
>     }

先の例では、簡単な説明をしただけなので、ロケールの切り替えがそれほど不便に感じられなかったかもしれません。しかし、コードの変換が関係してくると、これが非常に不便になります。

この例を説明するために、シフトシーケンスを使用した図 2 の JIS の符号化スキーマをもう一度参照してください。参考のため、図 8 に同じ内容を示します。この符号化では、文字シーケンスの構文解析時にシフトステートを維持する必要がありました。

#### 図 8 -- JIS で符号化した日本語テキスト (図 2 のもの)

例として、図 9 のように、JIS で符号化されたテキストを含む複数バイトファイルから入力を構文解析するものとします。このファイルの構文解析では、読み取る文字の変換方法を決定し、適切な内部ワイド文字に変換するには、現在のシフトステートを監視する必要があります。

#### 図 9 -- 大域的な C 言語での ロケールによる複数バイトファイルからの入力の構文解析

たとえば、入力として JIS 符号化スキーマを指定したロケールオブジェクトから、EUC 符号化スキーマを使用したロケールオブジェクトに切り換えるなど、構文解析時には大域的な C 言語でのロケールの切り替えができます。 ロケールを切り換えるたびに、現在のシフトステートは無効になるため、ロケールを切り換えるアプリケーションでは、シフトステートを正しく管理する必要があります。

ロケールの切り替えが意図的である限り、おそらく問題は解決できるでしょう。しかし、マルチスレッド環境では、大域的な C 言語でのロケールには深刻な問題があります。無関係のスレッドの実行によって、偶発的に切り替わることがあるためです。したがって、マルチスレッド環境の C プログラムを国際化するのは困難です。

一方、C+ ロケールの場合は、ストリームごとに別々のロケールオブジェクトに組み込んで、偶発的な切り替えを不可能にすればよいので、問題は簡単に解決することができます。次に、C++ ロケールの使用目的について説明します。

### 3.3.2 C++ ロケールの一般的な用途

C++ ロケールは、一般にデフォルトロケールとしてマルチロケールに、また大域ロケールとして使用します。

クラシックロケール。プログラムの国際化が必要ない場合は、C++ ロケールは不要で、C 言語でのロケールで十分です。アプリケーションのユーザーが、代表的なアメリカ英語の ASCII 動作を使用していることが確実な場合は、地域対応化機能は不要です。その場合は、アメリカ英語 ASCII ロケールに対応した、標準 C++ ライブラリの定義済みのロケールオブジェクトである `locale::classic()` を使用してください。

ネイティブロケール。ネイティブロケールとは、ユーザーやシステム管理者が任意に選択するロケールです。UNIX システムの場合、`LANG` などの環境変数で設定します。ネイティブロケールの C++ ロケールオブジェクトは、コンストラクタロケール`("")` を呼び出して作成します。
すなわち、空の文字列を名前に使用して、指定したロケールを要求します。指定した文字列が空の場合、システムは環境でロケール名を探します。これは、 C ライブラリの関数 `setlocale("")` と同じはたらきです。

指定ロケール。先に説明したように、ロケールには名前を設定することができます。クラシックロケールの名前は `"C"` です。他のロケールの名前は、プラットフォームによってまったく異なります。どのようなロケールがインストールされていて、またシステムではどのような名前になっているかは、システムのマニュアルを参照してください。システムに無効な名前でロケールを作成すると、コンストラクタによって `runtime_error` 例外処理が生成されます。

マルチロケール。C++ ロケールを使用すれば、異なるロケールによる作業は困難ではありません。C で行ったロケールの切り替えは C++ では不要です。ストリームごとに別々のロケールオブジェクトに組み込むことができます。また、複数の場所でロケールオブジェクトを受け渡しして使用することができます。

大域ロケール。C と同じく C++ にも大域ロケールがあります。大域ロケールは、最初は、前述のクラシックロケールになっています。大域ロケールを変更するには、`locale::global()` を呼び出します。

ロケールのデフォルトのコンストラクタである `locale::locale()` を呼び出すと、現在の大域ロケールのスナップショットを作成することができます。スナップショットは、不変のロケールオブジェクトであり、大域ロケールを変更しても変化しません。

入出力ストリームのような国際化構成要素では、デフォルトで大域ロケールを使用します。特定のロケールをストリームに明示的に組み込まない場合は、ストリームの作成時に大域ロケールのスナップショットがデフォルトで組み込まれます。

大域 C++ ロケールの使用方法は、C の場合とよく似ています。まず、プログラムの起動時にネイティブロケールを呼び出します。すなわち、 ネイティブロケールを大域ロケールにします。そして、以後のロケール依存のすべてのタスクに、そのスナップショットを使用します。以下のプログラム例は、この手順を示したものです。

>     locale::global(locale(""));                                   //1

>     string t = print_date(today, locale());                       //2

>     locale::global(locale("Fr_CH"));                              //3

>     cout << something;                                            //4

//1 | ネイティブロケールを大域化します。
//2 | ロケールオブジェクトが必要なときは、大域ロケールのスナップショットを使用します。print_date() が日付の書式設定をする関数の場合、書式設定をするときは、この関数で大域ロケールのスナップショットを参照します。
//3 | 大域ロケールを切り替えて、フランス語ロケールを大域ロケールにします。
//4 | この例では、`cout` の作成時に、クラシックロケールがプログラム起動時の大域ロケールだったため、標準ストリームの `cout` は、クラシックロケールに組み込まれたままになっています。大域ロケールを変更しても、既存のストリームのロケールは変わりません。 新しい大域ロケールに `cout` を組み込むには、`locale::global()`を呼び出してから `cout.imbue(locale())` を呼び出します。

### 3.3.3 C 言語でのロケールと C++ ロケールの関係

C 言語でのロケールと C++ ロケールは、基本的に無関係ですが、C++ ロケールオブジェクトに名前がある場合、`locale::global()` で大域ロケールにすると、setlocale() が呼び出され、C 言語でのロケールも変更されます。この場合は、C++ プログラムでロケール対応の C 関数を呼び出すことで、変更された C 言語でのロケールを使用することができます。

C プログラム内から C++ ロケールを制御することはできません。

Copyright (c) 1998, Rogue Wave Software, Inc.
このマニュアルに関する誤りのご指摘やご質問は、電子メールにてお送りください。

OEM リリース, 1998 年 6 月