发布时间:2023-07-10 22:59:03
当项目比较大时,错误类型会很多,需要不同类型的异常来帮我们细分错误。
曾经尝试用模板来实现,借助模板参数来细化。
然而效果并不好,因为经常感到作为模板参数的类型名无法体现异常含义,还是重新定义一个类更合适。
namespace YQ {
using std::exception;
using std::string;
using std::to_string;
template<typename ...Unused>
class location_exception : public exception {
public:
location_exception (const string &message, const string &filename, int line);
const char *what() const noexcept override;
protected:
string message;
};
}
namespace YQ {
template<typename ...Unused>
location_exception<Unused...>::location_exception (const string &message, const string &filename, int line)
{
this->message = "[" + filename + " : " + to_string (line) + "] " + message;
}
template<typename ...Unused>
const char *location_exception<Unused...>::what() const noexcept
{
return message.c_str();
}
}
这样使用时,需要针对某个已经存在的类来实例化异常模板(或者使用无模板参数的默认实例),这样的一个缺点前面已经说过。
灵活性较差,扩展起来也不方便。
首先定义一个基类exception,它提供行号、异常发生的文件。
异常的细化通过继承它来实现。
exception.h
#pragma once
#include
#include
namespace YQ {
using std::exception;
using std::string;
class Exception : public exception {
public:
virtual ~Exception() = default;//用作基类,所以要虚析构函数
Exception (const string &msg, const string &file, size_t line);
const char *what() const noexcept override;
private:
string m_buf;
string m_msg;
string m_file;
size_t m_line;
};
}
#define _Exception(func) YQ::Exception(#func"()",__FILE__,__LINE__)
exeception.cpp
#include "exception.h"
#include
#include
#include
namespace YQ {
using std::exception;
using std::string;
using std::to_string;
Exception::Exception (const string &msg, const string &file, size_t line)
: m_msg (msg), m_file (file), m_line (line)
{
m_buf = m_file + ":" + to_string (m_line) + ", " + m_msg + " : " + strerror (errno);
}
const char *Exception::what() const noexcept
{
return m_buf.c_str();
}
}
上面的exception类主要用做基类,具体的项目中,我们通过继承exception来细化异常,这非常简单:
struct IOException: public Exception {
using Exception::Exception;
};
struct ConfigException : public Exception {
using Exception::Exception;
};
#define _IOException(arg) IOException(#arg "()", __FILE__,__LINE__)
#define _ConfigException(arg) ConfigException(#arg "()", __FILE__,__LINE__)
使用了using指示帮助我们“继承”exception
的构造函数,避免重复实现。
额外定义辅助宏,使我们不用手动写__FILE__
、__LINE__
。
比如实现一个函数来读取文件整个内容,可以很轻松地找到错误位置和原因:
static string
read_whole_file (const char *file) noexcept (false)
{
FILE *pfile = fopen (file, "r");
if (pfile == NULL)
throw _IOException (fopen);
if (fseek (pfile, 0, SEEK_END) == -1) {
if (fclose (pfile) == EOF)
throw _IOException (fclose);
throw _IOException (fseek);
}
const auto file_size = ftell (pfile);
if (file_size == -1) {
if (fclose (pfile) == EOF)
throw _IOException (fclose);
throw _IOException (ftell);
}
rewind (pfile);
char buf[file_size];
size_t nread = fread (buf, sizeof (char), sizeof (buf), pfile);
if (nread != file_size) {
if (fclose (pfile) == EOF)
throw _IOException (fclose);
throw _IOException (fread);
}
if (fclose (pfile) == EOF)
throw _IOException (fclose);
return string (buf);
}
gcc编译器提供有__builtin_LINE()
、__builtin_FUNCTION()
、__builtin_FILE()
三个内置函数。
VS2019也加入了这些函数。
经实测,clang和MSVC均可使用这些函数,这样一来,也可以算作通用方法。
再借助C++函数默认参数,就可以避免使用__FILE__
、__LINE__
,也无需再定义宏。
只需要将上面的构造函数声明改一下即可:
Exception (const string &msg, const string &file = __builtin_FILE(), size_t line = __builtin_LINE());
无需借助宏来自动加上__FILE__
、__LINE__
、__FUNC__
。
要注意的是,如果你在默认参数中使用
__FILE__
、__LINE__
等宏,它会被预处理器原地替换,仅仅是默认参数所在行(函数声明处)的行号。