1、问题
编程的过程中,思考了一个问题。当一个局部的静态变量使用一个函数的返回值初始化时,如果该函数抛出异常,那么,局部静态变量是否被定义成功,即,如果再次调用包含局部静态变量的函数,抛出异常的函数会不会再次被调用。
2、测试
就此问题,我写了如下的测试程序:
#include <iostream>
#include <iomanip>
int func()
{
throw 100;
}
int TestStatic()
{
static int i=func();
return i;
}
int main(int argc, char * argv[])
{
try
{
std::cout<<TestStatic()<<std::endl;
}
catch(int i)
{
std::cout<<"exc1:"<<i<<std::endl;
try
{
std::cout<<"TestStatic:"<<TestStatic()<<std::endl;
}
catch(int i)
{
std::cout<<"exc2:"<<i<<std::endl;
}
}
return 0;
}
3、结果
1、在VC9中:第二次调用TestStatic函数不再抛出异常,即,局部静态变量已经定义成功。
2、在gcc中:第二次调用TestStatic函数依旧抛出异常,即,局部静态变量没有定义成功,需要再次进入定义,实现真正的内存分配空间。
对于结果,我更欣赏GNU的方式!
不管喜欢那种方式,测试结果都告诉我们,这段代码并部具有可移植性。VC的方式,让我比较失望,因为,因此而失去了一个通用的编程技巧。然而,知道什么是错的,比知道什么是对的还要重要,难道不是吗?
4、背景与应用
有朋友会问,这样的问题,什么情况下会遇到?
我只用一个比较经典的例子来说明。略微熟悉设计模式的朋友都会知道Singleton模式。一般返回的都是一个具体类的实例。虽说一般情况下构造函数中不要抛出异常,但免不了在某些情况下,可能会抛出异常,尤其是在这种单实例模式的情形下,没有必要一定保持构造函数中一定不抛出异常(stl要求容器中的类的构造函数不要抛出异常),所以,在这种情况下,我们似乎会希望,如果构造不成功,客户函数捕获异常,并修复错误后,再次获得单实例时,希望能够再次进行初始化,而得到一个正确的单实例的引用。然而,在这种情况下,面对两个编译器的测试结果,这段代码并不具有可移植性。
如果,只是如果,GNU编译器的方式是一个规范的话,那么,我们在Singleton模式下,就无需判断返回的对象是否有效,如果发生异常,也无须在实现类中编写弥补的代码,只需要再次重新获得Singleton即可,可以简化代码。但,这只是如果。
5、补充
回复 scwinter:
多谢兄弟关注!刚才我测试了一下,启用和未启用异常展开语义,编译的结果是不一样的。又掌握了一个知识点!结果如下:
E:/temp/C++>cl test.cpp
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 15.00.30729.01 版
版权所有(C) Microsoft Corporation。保留所有权利。
test.cpp
C:/Program Files/Microsoft Visual Studio 9.0/VC/INCLUDE/xlocale(342) : warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
Microsoft (R) Incremental Linker Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
test.obj
E:/temp/C++>test
exc1:100
TestStatic:0
E:/temp/C++>cl test.cpp /EHsc
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 15.00.30729.01 版
版权所有(C) Microsoft Corporation。保留所有权利。
test.cpp
Microsoft (R) Incremental Linker Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
test.obj
E:/temp/C++>test
exc1:100
exc2:100