前几日在看一个关于多线程下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之间的不同_第1张图片

这是刚才例子的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之间的不同_第2张图片
这是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之间的不同_第3张图片

假设你在解析一个含有在JIS下编码的文本的多字节文件,正如你在图9中所看到的。当你解析这个文件,你不得不跟踪shift状态以至于你可以知道如何翻译你所读到的字符节,如何以合适的宽字节来表达这些字符节。

Figure 9:使用全局C locale解析多字节文本输入
C Locale和C++ Locales之间的不同_第4张图片

在解析文本过程中,全局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的。