C Locale和C++ Locales之間的不同

前幾日在看一個關於多線程下locale問題:在多線程下locale not independent問題。無意中在網上搜到一篇講解C locale 和C++ locale的文章,覺得很好,鏈接如下:
http://stdcxx.apache.org/doc/stdlibug/24-3.html

今天嘗試翻譯一下,加深理解。翻譯如下:

Apache C++ 標準庫用戶嚮導
24.3 C Locale和C++ Locales區別
正如我們目前所看到的,C Locale和C++ Locales提供了類似服務。然而,C++ Locale所想表達的語義信息卻不同於C Locale。

  • 標準C locale是個全局設置,整個應用只有一個locale。因此一個應用很難一次處理多個locales。
  • 標準C++ locale是一個類。你可以創建出任意多的標準庫的實例。因此你想要多少locale 對象,你就能創建出來。

爲了進一步細節地探索他們的不同,我們來看看locales是如何被使用的。

24.3.1 C locale的一般使用
c locale 通常會被用作默認locale (defaule locale),本地locale (native locale),或者是多locale應用(multiple locale applications)中。

Default locale:作爲開發,如果你從不涉及到互聯網功能的開發需求,那麼你也不會調用std::setlocale()。如果你認爲你用戶總是在標準的US English ASCII下使用你的應用,那麼你也沒有localization的需求。如果你連locale都不知道的話,那麼你一定總是在默認的locale下,也就是在US English ASCII locale下開發你的應用。

Native locale:如果你確實有計劃在你的程序裏支持本地化,最合適的策略就是在程序一開始就獲取本地local信息,然後不要再更改設置。這樣你的應用會適配的選擇某個特定locale,並貫穿到應用的整個運行過程中。如此用戶在啓動應用之前就可以顯示的設置他們喜歡的locale。在UNIX系統中,他們通過設置環境變量,例如LANG來設置locale;其它操作系統有其它的方法去設置locale。

你在程序裏可以通過調用set::setlocale("")以及傳一個空字符串作爲locale的名字來指定一個用戶有偏好的locale設置。空的字符串是告訴setlocale去使用用戶在環境中指定的locale作爲應用的默認locale。

Multiple locales:有時候你不得不工作在多locales的情形下。例如,一個應用是給瑞士人使用的,那麼你期望輸出的消息能支持意大利語,法語和德語。因爲C locale是一個全局的(global)的數據結構,所以你必須在不同locales之間多次切換去支持不同語言。

我們來看一個在多locales情形下工作的例子。想象一下你的應用需要打印費用清單給全世界的顧客。那個費用清單一定是符合顧客本地語言的,所以你的應用就必須根據不同的語言打印不同語言費用清單。其中費用清單裏的價格是一個獨立的價格列表。假設我們的應用被一個美國公司使用,價格列表是US English。

應用在US English下讀價格列表(輸入),但是寫費用清單(輸出)是支持客戶本地語言的,比如說德語。因爲C有僅有一個全局的(global) locale,全局的locale會同時影響輸入和輸出,所以全局的locale需要在輸入和輸出之間進行切換。在價格從英文價格列表中讀出來之前,locale必須先從德文locale切換成US English locale。在價格加入到費用清單之前,全局的locale必須再切換成德文locale。爲了從價格列表裏讀下一個價格,locale又要切換爲English,如此反覆。 圖6解釋了這個過程。

Figure 6 : Multiple locales in C
C Locale和C++ Locales之間的不同

這是剛纔例子的C代碼:

double price;
char buf[SZ];
while ( ... )     // processing the German invoice 
{
   std::setlocale(LC_ALL, "En_US");
   std::fscanf(priceFile, "%lf", &price);

   // convert $ to DM according to the current exchange rate
   std::setlocale(LC_ALL, "De_DE");
   std::strfmon(buf,SZ, "%n", price);
   std::fprintf(invoiceFile, "%s", buf);
}

使用C++ locale對象可以極具地簡化這個過程。The iostreams in the C++ Standard Library are internationalized so that streams can be imbued with separate locale objects. C++標準庫下的iostreams是國際通用的(internationalized),所以streams能夠貫穿獨立的locale對象。例如,輸入流是English locale對象,輸出流是German locale對象。如此,locales切換就沒有必要了,正如圖7所顯示的:
Figure 7: Multiple locales in C++
C Locale和C++ Locales之間的不同
這是C++代碼的例子:

priceFile.imbue(std::locale("En_US"));
invoiceFile.imbue(std::locale("De_DE"));
moneytype price;
while ( ... )  // processing the German invoice
{
   priceFile >> price;
   // convert $ to DM according to the current exchange rate
   invoiceFile << price;
}

假設這個例子已經有

  • 一個類 moneytype, 來表示貨幣的價值,
  • 類moneytype提供了流insertion ( << ) 和 extraction ( >> )操作符
  • 使用std::money_put和std::money_get來格式化和解析貨幣值,流<<用std::money_put格式貨幣值;流>>用std::money_get解析貨幣值
    第26章列舉了一個較複雜的電話號碼的例子來闡釋這個技術。類moneytype不是C++標準庫的一部分。

貨幣值的例子相對簡單,在locales之間切換不是很麻煩。然而,一旦涉及到代碼層面切換,這個問題就變的主要起來了。

爲了更好的理解這一點,讓我們再看一下圖2中的JIS encoding scheme使用shift語法如何在圖8中變大方便起來的。正如你說記得那樣,當你解析字符句子的時候,你必須要維護一個shift狀態。
Figure 8: Figure 2中日文文本在JIS中的編碼
C Locale和C++ Locales之間的不同

假設你在解析一個含有在JIS下編碼的文本的多字節文件,正如你在圖9中所看到的。當你解析這個文件,你不得不跟蹤shift狀態以至於你可以知道如何翻譯你所讀到的字符節,如何以合適的寬字節來表達這些字符節。

Figure 9:使用全局C locale解析多字節文本輸入
C Locale和C++ Locales之間的不同

在解析文本過程中,全局C locale可以來回切換;例如,文本輸入的locale狀態從JIS 編碼切換到EUC編碼。每當locale切換到新的狀態,當前的locale狀態就無效了,在你的應用裏你不得不小心維護由locale切換帶來的狀態切換。

當local切換變得通用的時候,這個問題就迎刃而解了。然而,在多線程環境下,全局C locale會帶來嚴重的問題,因爲一個線程下的locale狀態不經意間會被另外一個線程下的locale給更改了。因爲這個原因,在多線程環境下通用化C程序就很困難。

如果你使用C++ locales,另一方面這個問題也很容易解決了。你可以每個數據流綁定到一個獨立的locale對象,這樣問題就不會出現locale被不期望修改的問題。讓我們來看一個C++ locales的例子。

priceFile.imbue(std::locale("En_US"));
invoiceFile.imbue(std::locale("De_DE"));
moneytype price;
while ( ... )  // processing the German invoice
{
   priceFile >> price;
   // convert $ to DM according to the current exchange rate
   invoiceFile << price;
}

24.3.2 C++ locales的一般使用
C++ locale通常被用作支持多locales的默認的locale;和全局locale。

Classic locale. 如果你涉及的程序並不需要支持本地化工作,那麼使用C++ locales和使用C locales沒什麼區別。 如果你總是認爲你的應用程序的用戶總是在US English ASCII環境下,那麼你也不需要請求本地化。對於你,C++標準庫提供了一個預定義的locale對象std::locale::classic(),它正是US English ASCII locale。

Native locale. Native locale是受用戶和系統管理員喜歡的一個locale。在UNIX系統裏,通常通過設置環境變量例如LANG來完成。你也可以通過調用構造函數std::locale("")爲native locale創建一個C++ locale對象,也就是通過請求一個以空字符串作爲輸入創建的locale。這個空字符串告訴系統從環境中得到locale的名字,這等同於C庫函數的std::setlocale("")。

Named locales. 正如上面所說,一個local可以有一個名字。標準的locale的名字是“c”。不幸的是,其他locales的名字平臺依賴。查詢你的系統文檔來獲知你的系統是什麼locales被安裝以及locale是如何命名的。如果你嘗試用一個無效的名字去創建一個locale對象,構造函數會拋出一個runtime_error異常。

Multiple locales. 當你使用C++ locales,工作在不同的locales就變得很容易了。你在C下面所做的locales切換,在C++裏就不再有必要了。你可以綁定每個流到不同的locale 對象。你也可以傳遞locales對象到不同地方使用,這完全沒有問題。

Global locale. 正如C,C++也有全局的locale。 一開始,全局locale是如前所說的標準locale。你也可以通過調用std::locale::global()去改變全局locale。

你也可以通過調用默認的構造函數std::locale::locale(),爲當前的全局locale創建一個快照。這些快照也是locales object獨立的,不受接下來的全局locale改變的影響。

通用化組件像iostreams,使用的全局locale作爲默認的locale。如果你不顯式的把一個流和一個特定的locale綁定在一起,那麼這個流對象就默認和一個在它被創建時的那個全局的locale綁定。

C下全局locale所能做的,C++全局locale也能做到。在程序啓動一旦開始,程序就激活了本地locale。換言之,本地locale被激活作爲global locale,並作爲locale快照,之後所有的工作都依賴這個locale。以下的代碼描述了這個過程:

std::locale::global(std::locale(""));                         //1
...
std::string t = print_date(today, std::locale());             //2
...
std::locale::global(std::locale("Fr_CH"));                    //3
...
std::cout << something;                                       //4

//1 設置本地locale作爲全局locale
//2 總是使用全局locale快照作爲你當前使用的locale對象。假設函數print_date()是格式化日期的。爲了格式化日期,你將要提供一個全局locale的快照給函數print_date()。
//3 切換全局locale到French全局locale
//4 注意在這個例子中,標準流cout仍然是跟classic locale(標準的locale)綁定的,因爲在程序啓動的時候,std::cout就被創建出來了。改變全局locale不會影響先前已經存在的流的locales。如果你想讓一個新的全局locale綁定到cout上,你應該在調用完std::locale::global()之後調用stf::cout.imbue(locale())。
24.3.3 C locale和C++ locales之間的聯繫

C locale和C++ locale是完全獨立的。然而,C++ locale對象是有個名字的,通過std::locale::global()使locale對象變成全局locale會引起C locale改變,這個改變會通過調用std::setlocale()。當它發生時,在C++程序裏,locale敏感的C函數會使用變化了的C locale。

在一個C程序裏,是沒有辦法改變C++ locale的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章