在带有msvc11的静态类析构函数中使用std::system_category()

Using std::system_category() in static class destructor with msvc11

本文关键字:system std category msvc11 静态类 析构函数      更新时间:2023-10-16

我对C++还很陌生,但在向Microsoft报告错误之前,我想确保我在这里没有做错什么。

以下是一些示例代码:

#include <system_error>
using namespace std;
class Test
{
public:
~Test()
{
throw system_error(5, system_category());
}
};
Test test;
void testfunc()
{
throw system_error(5, system_category());
}
void main()
{
try
{
testfunc();
}
catch ( const system_error& e)
{
}
}

现在,我希望Windows说"运行时以一种意想不到的方式请求程序退出"。然而,我得到了一个名为"纯虚拟函数"的错误。经过一点调试,我注意到当静态类析构函数获得std::system_category引用时,::name::message成员是纯虚拟的。然而,当它在testfunc()中构造时,那些vtable指针指向有效函数。

我的问题是,以这种方式构造system_error异常是不是做错了什么?我有一些代码,基本上是在做throw system_error(GetLastError(), system_category());。这恰好是在静态析构函数中执行的,我得到了一个名为error的纯虚拟函数。

要从Windows的GetLastError()函数抛出异常,我应该以不同的方式构造异常吗?还是这是msvc11的C++运行时中的一个错误?

编辑

我的问题有点混乱。我的实际代码比这个例子更复杂,实际上我没想到我的一个析构函数会抛出。我的析构函数必须调用一个可能引发的函数。如果我将代码更改为:

~Test()
{
try
{
callSomeFuncThatCouldThrow();
}
catch ( … ) { }
}

我将仍然得到纯虚拟函数调用错误。这是因为当构造system_error(在callSOmeFuncThatCouldThrow()中)时,它试图使用我给它的system_category::message成员,这会导致错误。

看起来像是一个Microsoft错误。std::error_category是各种未命名类型的抽象基类,其中一个是system_category()返回的类型。有一个该类型的对象,所有对system_category()的调用都返回对该对象的引用。您看到的情况看起来像是在test对象的析构函数运行之前,该对象正在被销毁。如果你想满足纯粹主义者,把你的析构函数改为:

Test::~Test() {
const std::error_category& cat = std::system_category();
std::cout << cat.name() << 'n';
}

这是Visual C++标准库实现中的一个错误。全局错误类别对象是使用类模板的静态数据成员实现的。不幸的是,在单个翻译单元(也称为源文件)中,这些数据成员将在翻译单元中的所有其他命名空间范围对象之后初始化,并在这些对象之前销毁(因为销毁的顺序与初始化相反)。

我的建议是避免在初始化和终止期间(即,在初始化所有静态对象之前或在开始销毁静态对象之后)调用generic_category()iostream_category()system_category()

如果无法做到这一点,则以下解决方法可能有效。它在我运行的几个简单测试中都有效,但我不能保证它在所有情况下的行为(这不是一个"官方"的解决方法;它只是一个潜在的解决方法)。将.cpp文件添加到您的项目中,其中包含以下内容:

// This is a workaround for a bug in the Visual C++ 2012 implementation of the
// global error category objects--generic_category(), iostream_category(), and
// system_category().
#ifdef _MSC_VER
#if _MSC_VER != 1700
#  error Please verify that this fix is still required and is still correct!
#endif
#include <system_error>
// Ensure that static objects in this translation unit get initialized "first":
#pragma warning(suppress: 4073)
#pragma init_seg(lib)
// Explicitly instantiate the global error objects in this translation unit:
template struct std::_Error_objects<int>;
#endif // _MSC_VER

#pragma init_seg(lib)应确保_Error_objects<int>的静态数据成员将在用户代码中的任何静态对象之前初始化。不要在此源文件中放入任何其他内容:#pragma init_seg应用于转换单元中的所有对象。_Error_objects是一个实现细节,该实现细节可能随时更改,因此编译器会进行版本检查。