1.优雅地细化异常

发布时间:2023-07-10 22:59:03

文章目录

    • 前言
    • 1. 旧的模板实现(不好)
    • 2. 新的继承实现
    • 3. 例子
    • 4. 编译器扩展

前言

当项目比较大时,错误类型会很多,需要不同类型的异常来帮我们细分错误。

曾经尝试用模板来实现,借助模板参数来细化。
然而效果并不好,因为经常感到作为模板参数的类型名无法体现异常含义,还是重新定义一个类更合适。

1. 旧的模板实现(不好)

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();
}

}

这样使用时,需要针对某个已经存在的类来实例化异常模板(或者使用无模板参数的默认实例),这样的一个缺点前面已经说过。
灵活性较差,扩展起来也不方便。

2. 新的继承实现

首先定义一个基类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__

3. 例子

比如实现一个函数来读取文件整个内容,可以很轻松地找到错误位置和原因:

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);
}

4. 编译器扩展

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__等宏,它会被预处理器原地替换,仅仅是默认参数所在行(函数声明处)的行号。

你可能感兴趣的:(项目小技巧,c++,c++)