2016-01-12 56 views
4

我想在逗号为小数点分隔符的德语区域设置中使用boost :: locale和std :: stod。考虑以下代码:stod无法正确使用boost :: locale

boost::locale::generator gen; 

std::locale loc(""); // (1) 
//std::locale loc = gen(""); // (2) 

std::locale::global(loc); 
std::cout.imbue(loc); 

std::string s = "1,1"; //float string in german locale! 
double d1 = std::stod(s); 
std::cout << "d1: " << d1 << std::endl; 

double d2 = 2.2; 
std::cout << "d2: " << d2 << std::endl; 

的std ::区域LOC( “”)创建正确的区域设置,输出是

d1: 1,1 
d2: 2,2 

如我所料。当我注释掉行(1)并取消注释行(2)时,输出为

d1: 1 
d2: 2.2 

d2的结果是预期的。据我了解boost :: locale希望我明确指出,应将d2格式化为一个数字,并执行

std::cout << "d2: " << boost::locale::as::number << d2 << std::endl; 

将输出再次修复为2,2。问题是,std :: stod不再认为1,1作为有效的浮点数了,并将其截断为1.

我的问题是: 为什么std :: stod停止工作时,我生成我的语言环境boost :: locale?

更多信息:我使用VC++ 2015年,升压1.60,没有ICU时,Windows 10

更新:

我注意到,当我设置全局区域设置两倍的问题是固定的,先用std :: locale(“”)然后用boost:

std::locale::global(std::locale("")); 
bl::generator gen; 
std::locale::global(gen("")); 

我不知道它为什么表现这种方式,不过!

+1

它会为您节省很多痛苦,长期来看,如果您要通过提供idx指针来检查解析错误std :: stod(str,** idx **) – ead

+0

是的,我在我真实的代码中执行了这个操作,但为了这个简单的测试删除了它。 – NicolasR

+0

好!我不知道这个项目有多大,但是改变全球的语言环境有可能在别的地方打破别的东西,但它应该是最后的选择......但是当你使用std :: stod时,你别无选择 – ead

回答

3

长话短说:boost::locale仅更改全局C++语言环境对象,但不更改C语言环境。 stod使用C语言环境而不是全局C++语言环境对象。 std::locale更改了全局C++语言环境对象和C语言环境。


原委:std::locale是一种微妙的东西,并负责了很多调试!

让我们先从C++类的std ::区域设置:

std::locale loc("de_DE.utf8"); 
    std::cout<<loc.name()<<"\n\n\n"; 

创建德语语言环境(如果可用你的机器上,否则它会抛出),这将导致在控制台上。

但它不会更改C++语言环境对象,该对象在程序启动时创建,并且是经典的(“C”),它不会更改全局 C++语言环境对象。的std::locale不带参数的构造函数返回的全局状态的副本:

... 
    std::locale loc2; 
    std::cout<<loc2.name()<<"\n\n\n"; 

现在你应该看到C如果没有之前搞砸了你的语言环境。std :: locale(“”)会做一些魔术,并找出用户的偏好并将其作为对象返回,而不是改变全局状态。在控制台上

std::locale::global(loc); 
    std::locale loc3; 
    std::cout<<loc3.name()<<"\n\n\n"; 

默认构造函数结果这次在:

您可以std::local::global改变局部的状态。 我们可以通过调用恢复全局状态古典:

std::locale::global(std::locale::classic()); 
    std::locale loc4; 
    std::cout<<loc4.name()<<"\n\n\n"; 

这应该给你再C

现在,当std :: cout被创建时,它会从全局C++状态克隆它的语言环境(这里我们用stringstreams来做,但它是一样的)。全局状态的后来的变化不影响数据流:

//classical formating 
    std::stringstream c_stream; 

//german formating: 
    std::locale::global(std::locale("de_DE.utf8")); 
    std::stringstream de_stream; 

    //same global locale, different results: 
    c_stream<<1.1; 
    de_stream<<1.1; 

    std::cout<<c_stream.str()<<" vs. "<<de_stream.str()<<"\n"; 

给你1.1 vs. 1,1 - 首先是经典的第二个德国

您可以imbue(std::locale::classic())改变流的本地语言环境的对象不言而喻不用说,这不会改变全局状态:

de_stream.imbue(std::locale::classic()); 
    de_stream<<" vs. "<<1.1; 
    std::cout<<de_stream.str()<<"\n"; 
    std::cout<<"global c++ state: "<<std::locale().name()<<"\n"; 

,你会看到:

1,1 vs. 1.1 
global c++ state: de_DE.utf8 

现在我们来到std::stod。正如你可以想像它采用全球C++语言环境(并非完全如此,多多包涵)状态,而不是cout -stream的(私人)状态:

std::cout<<std::stod("1.1")<<" vs. "<<std::stod("1,1")<<"\n"; 

给你1 vs. 1.1因为全局状态仍"de_DE.utf8" ,所以第一个解析停在'.',但std::cout的本地状态仍然是"C"。恢复全局状态后,我们得到了经典行为:

std::locale::global(std::locale::classic()); 
    std::cout<<std::stod("1.1")<<" vs. "<<std::stod("1,1")<<"\n"; 

现在德国"1,1"不能正常解析:1.1 vs. 1

现在,你可能会认为,我们都做了,但还有更多 - 我答应告诉你约std::stod

除了全局C++语言环境,还有所谓的(全局)C语言环境(来自C语言,不要与经典的“C”语言环境混淆)。每次我们改变全局C++语言环境时,C语言环境也被改变了。

获取/设置C语言环境可以使用std::setlocale(...)完成。查询当前值运行:

std::cout<<"(global) C locale is "<<std::setlocale(LC_ALL,NULL)<<"\n"; 

(global) C locale is C。要设置的C语言环境运行:

assert(std::setlocale(LC_ALL,"de_DE.utf8")!=NULL); 
    std::cout<<"(global) C locale is "<<std::setlocale(LC_ALL,NULL)<<"\n"; 

这产生(global) C locale is de_DE.utf8。但是现在全球的C++语言环境是什么?

std::cout<<"global c++ state: "<<std::locale().name()<<"\n"; 

如您所料,C对C++全局语言环境一无所知,并保持不变:global c++ state: C

现在我们不再在堪萨斯了!旧的c函数将使用C语言环境和新的C++函数全局C++。鼓励自己进行有趣的调试!

,你会想到

std::cout<<"C: "<<std::stod("1.1")<<" vs. DE :"<<std::stod("1,1")<<"\n"; 

做什么? std::stod毕竟是全新的C++ 11功能,它应该使用全局C++语言环境!再想想...:

1 vs. 1.1 

它得到了德国格式正确,因为C语言环境被设置为“de_DE.utf8”,它的引擎盖下使用旧的C风格的函数。

只是为了完整起见,在std::streams使用全局C++语言环境:

std::stringstream stream;//creating with global c++ locale 
    stream<<1.1; 
    std::cout<<"I'm still in 'C' format: "<<stream.str()<<"\n"; 

为您提供:I'm still in 'C' format: 1.1

编辑:的另一种方法来解析串没有与全局区域设置或搞乱通过它受到干扰:

bool s2d(const std::string &str, double &val, const std::locale &loc=std::locale::classic()){ 

    std::stringstream ss(str); 
    ss.imbue(loc); 
    ss>>val; 
    return ss.eof() && //all characters interpreted 
     !ss.fail(); //nothing went wrong 
} 

以下测试表明:

double d=0; 
    std::cout<<"1,1 parsed with German locale successfully :"<<s2d("1,1", d, std::locale("de_DE.utf8"))<<"\n"; 
    std::cout<<"value retrieved: "<<d<<"\n\n"; 

    d=0; 
    std::cout<<"1,1 parsed with Classical locale successfully :"<<s2d("1,1", d, std::locale::classic())<<"\n"; 
    std::cout<<"value retrieved: "<<d<<"\n\n"; 

    d=0; 
    std::cout<<"1.1 parsed with German locale successfully :"<<s2d("1.1", d, std::locale("de_DE.utf8"))<<"\n"; 
    std::cout<<"value retrieved: "<<d<<"\n\n"; 

    d=0; 
    std::cout<<"1.1 parsed with Classical locale successfully :"<<s2d("1.1", d, std::locale::classic())<<"\n"; 
    std::cout<<"value retrieved: "<<d<<"\n\n"; 

即仅在第一和最后的转换成功:

1,1 parsed with German locale successfully :1 
value retrieved: 1.1 

1,1 parsed with Classical locale successfully :0 
value retrieved: 1 

1.1 parsed with German locale successfully :0 
value retrieved: 11 

1.1 parsed with Classical locale successfully :1 
value retrieved: 1.1 

std :: stringstream可能不是最快的,但有其优点... ...

+0

感谢这个真棒描述!虽然它不完全回答我的问题,但它只是一些步骤现在可以自己找到它设置std :: locale(“”)作为全局区域设置成功地改变了C区域设置,但不是de_DE.utf8,而是在Windows下的German_Germany.1252。 ,不会改变C语言环境,并且会破坏我的语言!因此,唯一没有明确提到某些语言环境名称的解决方案,它似乎在Windows和* nix系统之间不同,而且我事先并不知道,它正在设置现场两次 – NicolasR

+0

...正如我在上面的更新中所示。 – NicolasR