我创建了一个模板异常类wexception,它接受wstring消息,将它们存储为UTF-8编码的string,并在检索时将其转换回wstring。代码中包含了一些特定于MS C++的部分:std::exception::exception(char*)构造函数以及用于转换为和从UTF-8的MultiByteToWideChar和WideCharToMultiByte函数,以及declspec(selectany)指令,该指令指示链接器只创建一个已定义多次的符号的实例。模板参数可以是任何具有以char*参数为输入的构造函数的异常类。代码不使用概念(concepts),以便在较旧的语言标准下编译。还有改进的空间。
#include <Windows.h>
#include <exception>
#include <string>
class wexception_base
{
public:
static std::wstring wwhat(std::exception const& e)
{
return FromUtf8OrAnsi(e.what());
}
static bool IsUtf8Bom(char const* pmsg)
{
if (pmsg == nullptr)
return false;
for (size_t i = 0; i < sizeof(m_Utf8Bom); ++i)
if (pmsg[i] != m_Utf8Bom[i])
return false;
return true;
}
static std::wstring FromUtf8OrAnsi(char const* pmsg)
{
if (pmsg == nullptr)
return {};
size_t len = strlen(pmsg);
bool const isUtf = IsUtf8Bom(pmsg);
if (isUtf)
{
pmsg += sizeof(m_Utf8Bom);
len -= sizeof(m_Utf8Bom);
if (len == 0)
return {};
}
int const cp = isUtf ? CP_UTF8 : CP_ACP;
size_t required = MultiByteToWideChar(cp, 0, pmsg, -1, nullptr, 0);
_ASSERT(required > 0);
_ASSERT(required <= INT_MAX);
if (required > INT_MAX - 16)
required = INT_MAX - 16;
std::vector<wchar_t> buffer(required + 1);
auto const used = MultiByteToWideChar(cp, 0, pmsg, -1, buffer.data(), static_cast<int>(buffer.size()));
_ASSERT(used > 0);
return buffer.data();
}
static std::string ToUtf8(wchar_t const* pmsg)
{
size_t required = WideCharToMultiByte(CP_UTF8, 0, pmsg, -1, nullptr, 0, nullptr, nullptr);
_ASSERT(required > 0);
_ASSERT(required <= INT_MAX);
if (required >= INT_MAX)
return {};
std::vector<char> buffer(required + sizeof(m_Utf8Bom) + 1);
memcpy_s(buffer.data(), required + 3, m_Utf8Bom, sizeof(m_Utf8Bom));
auto const used = WideCharToMultiByte(CP_UTF8, 0, pmsg, -1, buffer.data() + sizeof(m_Utf8Bom), static_cast<int>(buffer.size()), nullptr, nullptr);
_ASSERT(used > 0);
return buffer.data();
}
static char m_Utf8Bom[3];
};
__declspec(selectany) char wexception_base::m_Utf8Bom[3] = {'\xEF', '\xBB', '\xBF'};
template<class BASE = std::exception>
class wexception : public BASE, public wexception_base
{
public:
using BASE::BASE;
template<typename... ARGS>
wexception(wchar_t const* message, ARGS&&... args) : BASE(ToUtf8(message).c_str(), std::forward<ARGS>(args...))
{
static_assert(std::is_base_of_v<std::exception, BASE>, "BASE must be std::exception, or derived from std::exception");
}
template<typename... ARGS>
wexception(std::wstring const& message, ARGS&&... args) : BASE(ToUtf8(message.c_str()).c_str(), std::forward<ARGS>(args)...)
{
static_assert(std::is_base_of_v<std::exception, BASE>, "BASE must be std::exception, or derived from std::exception");
}
template<typename... ARGS>
wexception(std::string const& message, ARGS&&... args) : BASE(message.c_str(), std::forward<ARGS>(args)...)
{
static_assert(std::is_base_of_v<std::exception, BASE>, "BASE must be std::exception, or derived from std::exception");
}
};
你可以将
wexception
的实例作为任何异常类来抛出和捕获。捕获语句的写法如下:
catch (std::exception const& e)
{
std::wstring msg = wexception<>::wwhat(e);
std::wcerr << msg << std::endl;
return;
}
静态函数
wwhat
通过 BOM(字节顺序标记)识别 UTF-8 字符串,并假设不以 BOM 开头的字符串为 ANSI 编码。无论如何,您将获得一个带有异常消息的
wstring
。
std::exception
的更大问题是派生类从中非虚拟派生。由于这个原因,你无法从自己的基类派生,该基类又从std::exception
派生,然后再从std::out_of_range
派生。 - sbistd::exception
来定义我的继承关系来避免这个问题。我抛出自己派生的std::exception
异常,并将其他标准定义的异常留给标准库。虽然不是理想的解决方案,但对于我的用途来说,考虑到标准的当前状态,这是最好的解决方案。 - John Dibling