在 C++ 中创建自己的错误处理机制

creating own error handling mechanism in c++

本文关键字:处理 处理机 机制 错误 自己的 C++ 创建      更新时间:2023-10-16

我想创建一个类和枚举来处理项目中的错误。截至目前,我正在以以下方式执行此操作。

enum class eErrorType
{
eJsonFileNotFound = 0,
eJsonInvalidFormat,
eJsonKeyNotFound,
eJsonEmptyArray,
eNoError,
eCustom
};

class Error
{
public:
// Constructors                                                                     
Error() { errorType = eErrorType::eNoError; message = ""; }
Error(eErrorType type) { errorType = type; SetMessage(type); }
Error(std::string msg) { errorType = eErrorType::eCustom; message = msg; }
// Public Methods                                                                   
std::string getErrMessage() { return message; }

private:
eErrorType errorType;
std::string message;
void SetMessage(eErrorType type)
{
switch (type)
{
case eErrorType::eJsonFileNotFound: message = "Json file not found"; break;
case eErrorType::eJsonInvalidFormat: message = "Invalid json file"; break;
case eErrorType::eJsonKeyNotFound: message = "Specified key is not found in json"; break;
case eErrorType::eJsonEmptyArray: message = "No elements in json array"; break;
case eErrorType::eNoError: message = "Entry contained an attempt to divide by zero!"; break;
default: message = ""; break;
}
}
};
int main()
{
try
{
//open json file. If file open failed, throw error
throw eErrorType::eJsonFileNotFound;
//parse json file. If parsing failed, throw error
throw eErrorType::eJsonInvalidFormat;
//check for particular key in the json. If not found, throw error
throw eErrorType::eJsonKeyNotFound;
}
catch (eErrorType errCode)
{
Error errObj(errCode);
std::cout << errObj.getErrMessage() << std::endl;
}
return 0;
}

我想要一些改进建议。有没有更好的方法,或者任何基于语言的功能都可以实现这一点。

对于自定义错误,您可以从 std::exception 继承,覆盖异常方法并实现你自己的东西,例如:

#include <exception>    // std::exception
//
// custom exception class
//
class error final :
public std::exception
{
public:
error(const char* description, short code = -1) throw() :
description(description), code(code) { }
const char* what() const throw() override
{
return description;
}
short Code() const throw()
{
return code;
}
error(error&& ref)
: description(ref.description), code(ref.code) { }
error& operator=(error&&)
{
return *this;
}
error(const error& ref)
: description(ref.description), code(ref.code) { }
private:
const char* description;
const short code;
error& operator=(const error&) = delete;
};

定义一个宏以显示发生错误的文件名:

#include <cstring>      // std::strrchr
// Show only file name instead of full path
#define __FILENAME__ (std::strrchr(__FILE__, '') ? std::strrchr(__FILE__, '') + 1 : __FILE__)

定义通用函数以显示错误(随后显示消息框,但您可以为控制台程序重新定义它)

#include <string>
#include <windows.h>
#include <codecvt>      // string conversion std::wstring_convert and std::codecvt_utf8
//
// converts string or const char* to wstring
//
std::wstring stringToWstring(const std::string& t_str)
{
//setup converter
typedef std::codecvt_utf8<wchar_t> convert_type;
std::wstring_convert<convert_type, wchar_t> converter;
//use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
return converter.from_bytes(t_str);
}
//
// Set error message to your liking using error class
// and show message box, this function is also used to pass in
// std::exception objects
//
template <typename ExceptionClass>
void ShowError(
HWND hWnd,
ExceptionClass exception,
const char* file,
int line,
long info = MB_ICONERROR)
{
std::string error_type = TEXT("Rutime Error");
std::string error_message = TEXT("File:t");
#ifdef UNICODE
error_message.append(stringToWstring(file));
#else
error_message.append(file);
#endif // UNICODE
error_message.append(TEXT("rnLine:t"));
error_message.append(std::to_string(line));
error_message.append(TEXT("rnError:t"));
#ifdef UNICODE
error_message.append(stringToWstring(exception.what()));
#else
error_message.append(exception.what());
#endif // UNICODE
// Show error message
MessageBox(hWnd,
error_message.c_str(),
error_type.c_str(), static_cast<UINT>(MB_OK | info));
}

然后,您像这样显示错误:

ShowError(nullptr, error("You error message"), __FILENAME__, __LINE__);

对于 Win32/COM 错误类型,函数可以像这样重载:

#include <comdef.h>     // _com_error
#include <windows.h>
#include <string>
//
// Format error code into a string and show message box
// COM and System errors
//
void ShowError(HWND hWnd, const char* file, int line, HRESULT hr = S_OK)
{
string error_type = TEXT("Rutime Error");
string error_message = TEXT("File:t");
#ifdef UNICODE
error_message.append(stringToWstring(file));
#else
error_message.append(file);
#endif // UNICODE
error_message.append(TEXT("rnLine:t"));
error_message.append(std::to_string(line));
error_message.append(TEXT("rnError:t"));
// If HRESULT is omited or S_OK
// format last error code message
if (hr == S_OK)
{
LPVOID lpBuff = nullptr;
DWORD dwChars = FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
nullptr,
GetLastError(),
0,
reinterpret_cast<LPTSTR>(&lpBuff),
0,
nullptr);
// If the function succeeds, the return value is
// the number of TCHARs stored in the output buffer
if (dwChars)
{
error_message.append(reinterpret_cast<LPCTSTR>(lpBuff));
}
else // If the function fails, the return value is zero
{
error_message.append(TEXT("Unknown Errort"));
error_message.append(to_string(GetLastError()));
}
// Free the buffer allocated by FormatMessage
LocalFree(lpBuff);
}
else // Format com error code into a message
{
_com_error err(hr);
error_message.append(err.ErrorMessage());
}
// Play the sound and show error message
MessageBox(hWnd,
error_message.c_str(),
error_type.c_str(), MB_OK | MB_ICONERROR);
}

对于系统错误,该函数的调用略有不同:

ShowError(nullptr, __FILENAME__, __LINE__); // type hresult if needed

编辑:我从我的项目中复制了代码,目前std::to_string提到的仅适用于 ANSI 版本的地方,您需要修改ShowError函数以有条件地将std::to_wstring用于 unicode。 另外stringShowError 函数内部是 ANSI 字符串,如果您不喜欢这样,您可以有条件地使用wstring或为字符串定义一个宏:

#ifdef UNICODE
typedef std::wstring string;
#else
typedef std::string string;
#endif // UNICODE

如果您愿意,也可以to_string

// conditionaly use string or wide string
#ifdef UNICODE
#define to_string std::to_wstring
#else
#define to_string std::to_string
#endif // UNICODE

您还可以实现enum class代码类型,并将它们作为第二个或第三个附加参数传递给异常类,如果您希望避免为每个单独的函数调用键入错误消息,则可以实现显示自定义错误代码。

另请注意,ShowError函数可用于 catch 语句中的 std 错误,您只需像这样传递 std 错误对象

,例如:
try
{
// example, or do some heavy memory allocation here to throw
throw std::bad_alloc;
}
catch(std::bad_alloc& err);
{
ShowError(nullptr, err, __FILENAME__, __LINE__);
}

可以扩展此方法以修改函数以设置NT状态消息的格式

有关 Win32 中可能错误消息的完整列表,请参阅此处。

有关上述代码中使用的函数的其他信息,请参阅以下链接:

格式化消息功能

获取上一个错误函数

一些代码是从这个网站复制的,例如:

转换为字符串

仅显示文件名

格式化 COM 错误代码

让我们从标题开始。

#include <system_error>   // For std::error_code
#include <exception>      // For std::exception
#include <iostream>       // To print example output

通常你会被一些宏所困(可能是constexpr int的)

#define FOO_ERROR1 1
#define FOO_ERROR2 2

您应该在命名空间中工作,让我们在此处使用n

namespace n {

第 1 步:定义枚举类型(您可能已经有一个枚举,这很好)。

enum class Error {
XE_FOO_ERROR1 = FOO_ERROR1, // XE_ is a random prefix to distinguish it from the macro FOO_ERROR1.
XE_FOO_ERROR2 = FOO_ERROR2
};

步骤 2. 定义将通过 ADL(依赖于参数的查找)找到的以下函数。这就是为什么命名空间(n)对于这种错误类型应该非常具体的原因。例如,我用于xcb系统错误的命名空间是xcb::errors::org::freedesktop::xcb.只是n可能太短了,无论如何我们都应该使用foo,但我认为使用不同名称的示例不会那么令人困惑。

std::string to_string(Error code);                        // You probably want those enums to be convertible to strings with `to_string`.
std::ostream& operator<<(std::ostream& os, Error code);   // In order to print the enum's to an ostream.
inline char const* get_domain(Error) { return "foo:org.freedesktop.foo.Error"; }  // An error domain string.
std::error_code make_error_code(Error code) noexcept;     // Converting the Error to a std::error_code.
} // namespace n

步骤 3. 将 n::Error 注册为有效的错误代码。

namespace std {
template<> struct is_error_code_enum<n::Error> : true_type { };
} // namespace std

是的,在命名空间std中定义它是合法的。这是标准的意图。

接下来,让我们在.cpp文件中定义上述函数:

namespace n {
// Implement to_string
std::string to_string(Error code)
{
#if 0
switch(code)
{
case Error::XE_FOO_ERROR1:
return "FOO_ERROR1";
case Error::XE_FOO_ERROR2:
return "FOO_ERROR2";
}
return "Never reached";
#else
// Or if you use magic_enum (https://github.com/Neargye/magic_enum), we can use the simple:
auto sv = magic_enum::enum_name(code);
sv.remove_prefix(3);  // Remove "XE_" from the name.
return std::string{sv};
#endif
}
// Implement operator<<
std::ostream& operator<<(std::ostream& os, Error code)
{
return os << to_string(code);
}

在我们定义make_error_code之前,我们首先需要定义错误类别(这仍然在同一个.cpp文件中!

namespace {
struct FooErrorCategory : std::error_category
{
char const* name() const noexcept override;
std::string message(int ev) const override;
};
char const* FooErrorCategory::name() const noexcept
{
return "n::Error"; // Can be anything you want.
}
std::string FooErrorCategory::message(int ev) const
{
auto error = static_cast<Error>(ev);
return to_string(error);
}
FooErrorCategory const theFooErrorCategory { };
} // namespace

现在我们可以定义make_error_code.

std::error_code make_error_code(Error code) noexcept
{
return std::error_code(static_cast<int>(code), theFooErrorCategory);
}
} // namespace n

为了支持异常,我们需要一些支持std::error_code的异常类。因此,这是一个通用类,而不是特定于一个n::Error枚举的东西。就个人而言,我使用了一个专门的系统,你可以在这里找到,但让我们为这个例子编写一些最少的东西(这是头文件):

namespace errors {
struct ErrorCode : public std::exception
{
std::error_code m_ec;
char const* m_text;
ErrorCode(std::error_code ec, char const* text) : m_ec(ec), m_text(text) {}
void print_on(std::ostream& os) const;
};
inline std::ostream& operator<<(std::ostream& os, ErrorCode const& ec)
{
ec.print_on(os);
return os; 
}
} // namespace errors

和.cpp文件

namespace errors {
void ErrorCode::print_on(std::ostream& os) const
{
os << m_text << ": " << m_ec.message() << " [" << m_ec << "]";
}
} // namespace errors

最后,这里有一些代码来测试上述内容。 你也可以在wandbox上找到这个完整的例子。

int main()
{
// A test enum error code.
n::Error error = n::Error::XE_FOO_ERROR2;
// We want to be able to use to_string.
using std::to_string; // Fall back (that we are not using here).
std::cout << to_string(error) << std::endl;
// We want to be able to print an Error.
std::cout << error << std::endl;
// We want to be able to convert the error to a std::error_code.
std::error_code ec = error;
// We want to be able to print that error_code.
std::cout << ec << " [" << ec.message() << "]n";
// You can convert a macro (or int) value to the enum type.
n::Error e1 = static_cast<n::Error>(FOO_ERROR1);
// We want to be able to throw 'Error' values.
try
{
throw errors::ErrorCode(e1, "Some text");
}
catch (errors::ErrorCode const& error)
{
std::cout << "Caught: " << error << std::endl;
}
}

哪些输出

FOO_ERROR2
FOO_ERROR2
n::Error:2 [FOO_ERROR2]
Caught: Some text: FOO_ERROR1 [n::Error:1]