我有一个Visual Studio 2008 C ++项目,在出现例外错误的情况下使用Win32Exception
类。 Win32Exception
类看起来像这样:
/// defines an exception based on Win32 error codes. The what() function will
/// return a formatted string returned from FormatMessage()
class Win32Exception : public std::runtime_error
{
public:
Win32Exception() : std::runtime_error( ErrorMessage( &error_code_ ) )
{
};
virtual ~Win32Exception() { };
/// return the actual error code
DWORD ErrorCode() const throw() { return error_code_; };
private:
static std::string ErrorMessage( DWORD* error_code )
{
*error_code = ::GetLastError();
std::string error_messageA;
wchar_t* error_messageW = NULL;
DWORD len = ::FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
*error_code,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
reinterpret_cast< LPWSTR >( &error_messageW ),
0,
NULL );
if( NULL != error_messageW )
{
// this may generate a C4244 warning. It is safe to ignore.
std::copy( error_messageW,
error_messageW + len,
std::back_inserter( error_messageA ) );
::LocalFree( error_messageW );
}
return error_messageA;
};
/// error code returned by GetLastError()
DWORD error_code_;
}; // class Win32Exception
该课程在其使用的情况下运作良好。我想知道的是,如果有任何明显的情况会导致失败,我应该知道。欢迎任何其他陷阱,警告或改进的一般建议。
请注意,升级库不是此代码的选项。
答案 0 :(得分:5)
具有讽刺意味的是,您的代码并非异常安全。
if( NULL != error_messageW )
{
// this may generate a C4244 warning. It is safe to ignore.
std::copy( error_messageW,
error_messageW + len,
std::back_inserter( error_messageA ) );
::LocalFree( error_messageW );
}
请注意,如果back_inserter
导致std::bad_alloc
被抛出,则FormatMessage
内分配的内存将被泄露。
答案 1 :(得分:3)
多么巧合!我在所有项目中都使用了类似的代码!这实际上是一个好主意。
此代码存在问题:
// this may generate a C4244 warning. It is safe to ignore.
std::copy( error_messageW,
error_messageW + len,
std::back_inserter( error_messageA ) );
它只是将WCHAR转换为字符。你可以显式地使用FormatMessageA来获取当前代码页中的消息(好吧,你不能像你说的那样),或者使你的所有行为都是UTF-8编码的约定。我之后选择了this为什么。
错误消息本身可能没用。捕获堆栈跟踪可能是一个好主意。
答案 2 :(得分:1)
FormatMessage
本身可能会失败。对于这种情况,可能会出现一些中性的“代码%d的未知错误”。SHFileOperation
)。如果你想要它们被处理,那就是。 答案 3 :(得分:1)
我想知道的是,如果有的话 这是否会出现任何明显的情况 失败,我应该知道。任何 其他陷阱,警告或一般 关于改进的建议是 欢迎。
我在使用此类邮件检索时遇到的主要问题是ERROR_SUCCESS
。当某些操作失败时,它会相当令人困惑,并伴有错误消息“操作成功”。人们不会认为这可能会发生,但确实如此。
我想这是Dialecticus注意到的一个特例,“有些错误代码并非真正的错误”,但对于大多数这些代码,至少该消息通常是可以接受的。
第二个问题是大多数Windows系统错误消息在末尾都有回车符+换行符。将消息插入到其他文本中会出现问题,并且会破坏C ++异常消息的约定。所以,最好删除那些字符。
现在,不要重复其他人已经注意到的所有内容,而是要谈谈设计。
如果公开或移出类,ErrorMessage
函数将更有用,并且按值获取错误代码,而不是使用指针参数。这是将单独责任分开的原则。促进重用。
如果使用析构函数释放内存,ErrorMessage
中的代码将更加清晰,安全和高效。然后你也可以直接在return
语句中构造字符串,而不是使用带有后插入器的复制循环。
干杯&amp;第h。,
答案 4 :(得分:0)
我最近在一个非常相似的类上工作,在阅读完这个线程后试图使复制部分异常安全。我介绍了一个小助手类,除了保存指向::FormatMessage
返回的字符串的指针,并在其析构函数中用::LocalFree
释放它时,它什么都不做。不允许复制,分配和移动,因此不会遇到麻烦。
以下是我的总结:
class windows_error {
public:
windows_error(wchar_t const* what);
// Getter functions
unsigned long errorCode() const { return _code; }
wchar_t const* description() const { return _what; }
std::wstring errorMessage() const { return _sys_err_msg; }
private:
unsigned long _code;
wchar_t const* _what;
std::wstring _sys_err_msg;
};
// This class outsources the problem of managing the string which
// was allocated with ::LocalAlloc by the ::FormatMessage function.
// This is necessary to make the constructor of windows_error exception-safe.
class LocalAllocHelper {
public:
LocalAllocHelper(wchar_t* string) : _string(string) { }
~LocalAllocHelper() {
::LocalFree(_string);
}
LocalAllocHelper(LocalAllocHelper const& other) = delete;
LocalAllocHelper(LocalAllocHelper && other) = delete;
LocalAllocHelper& operator=(LocalAllocHelper const& other) = delete;
LocalAllocHelper& operator=(LocalAllocHelper && other) = delete;
private:
wchar_t* _string;
};
windows_error::windows_error(wchar_t const* what)
: _code(::GetLastError()),
_what(what) {
// Create a temporary pointer to a wide string for the error message
LPWSTR _temp_msg = 0;
// Retrieve error message from error code and save the length
// of the buffer which is being returned. This is needed to
// implement the copy and assignment constructor.
DWORD _buffer_size = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, _code, 0, _temp_msg, 0, NULL);
if(_buffer_size) {
// When calling _sys_err_msg.resize an exception could be thrown therefore
// the _temp_msg needs to be a managed resource.
LocalAllocHelper helper(_temp_msg);
_sys_err_msg.resize(_buffer_size + 1);
std::copy(_temp_msg, _temp_msg + _buffer_size, _sys_err_msg.begin());
}
else {
_sys_err_msg = std::wstring(L"Unknown error. (FormatMessage failed)");
}
}
也许这对你们中的一些人有用。
答案 5 :(得分:0)
意识到这是旧的,但至少在VC ++ 2015中你可以使用system_error
函数抛出system_category()
来完成所有这些操作:
try
{
throw system_error(E_ACCESSDENIED, system_category(), "Failed to write file");
}
catch (exception& ex)
{
cout << ex.what();
}
这将打印:“无法写入文件:访问被拒绝”