我想在C ++中实现一个Exception类,它模仿.NET框架中的一个(并且Java也有类似的东西),用于以下目的:
异常链接:我想实现“异常翻译”的概念,当更高级别捕获的异常包装并“翻译”低级别异常时,也会以某种方式保留这些情人级异常(在InnerException
成员,在这种情况下)。为此,应该有一些机制来存储内部异常以及在上层抛出的每个异常。 InnerException
成员在下面的实现中提供了这一点。
异常继承:例如,应该可以从IoException
派生Exception
,从SerialPortException
派生IoException
。虽然这看起来微不足道,但应该有能力动态识别捕获的异常类型(例如,用于记录目的,或显示给用户),最好没有RTTI和typeid
的开销。
这是我想要实现的示例异常处理逻辑:
try
{
try
{
try
{
throw ThirdException(L"this should be ThirdException");
}
catch(Exception &ex)
{
throw SubException(L"this should be SubException", ex);
}
}
catch(Exception &ex)
{
throw SubException(L"this should be SubException again", ex);
}
}
catch(Exception &ex)
{
throw Exception(L"and this should be Exception", ex);
}
当在最上层中捕获“最外层”异常时,我希望能够通过InnerException
成员解析和格式化整个异常链,以显示如下内容:
到目前为止,我已经提出了以下实现:
小注意:CString
是特定于Microsoft的字符串类(仅适用于不熟悉Visual C ++内容的人)。
class Exception
{
protected:
Exception(const Exception&) {};
Exception& operator= (const Exception&) {};
public:
Exception(const CString &message) : InnerException(0), Message(message) {}
Exception(const CString &message, const Exception &innerException) : InnerException(innerException.Clone()), Message(message) {}
virtual CString GetExceptionName() const { return L"Exception"; }
virtual Exception *Clone() const
{
Exception *ex = new Exception(this->Message);
ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
return ex;
}
public:
virtual ~Exception() { if (InnerException) delete InnerException; }
CString Message;
const Exception *InnerException;
};
现在我们在这里有什么。复制构造函数和赋值运算符为protected
以防止复制。每个对象将“拥有”其内部异常对象(并在析构函数中删除它),因此默认的浅拷贝将是不可接受的。然后我们有两个非常标准的构造函数和虚拟析构函数来删除InnerException
对象。 Clone()
虚方法负责深度复制对象,主要用于存储内部异常对象(请参阅第二个构造函数)。最后GetExceptionName()
虚方法提供了RTTI的廉价替代方法来识别异常类名称(我不认为这看起来很酷但我无法提出更好的解决方案;为了比较:在.NET中,可以简单地使用someException.GetType().Name
)。
现在这样做了。但是......出于某个特殊原因,我不喜欢这个解决方案:每个派生类所需的编码量。考虑我必须派生SubException
类,它提供对基类功能的绝对零添加,它只提供自定义名称(“SubException”,可能是“IoException”,“ProjectException”,...)区分它的使用场景。我必须为每个这样的异常类提供几乎相同数量的代码。这是:
class SubException : public Exception
{
protected:
SubException(const SubException& source) : Exception(source) {};
SubException& operator= (const SubException&) {};
public:
SubException(const CString &message) : Exception(message) {};
SubException(const CString &message, const Exception &innerException) : Exception(message, innerException) {};
virtual CString GetExceptionName() const { return L"SubException"; }
virtual Exception *Clone() const
{
SubException *ex = new SubException(this->Message);
ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
return ex;
}
};
我不喜欢每次都必须提供protected
拷贝构造函数和赋值运算符的事实,我不喜欢每次都必须克隆Clone
方法的事实,复制甚至复制基础成员的代码(InnerException
...),简单......我不认为这是优雅的解决方案。但我无法想到更好的一个。您是否有任何想法如何“正确”实施这一概念?或者这可能是C ++中这个概念的最佳实现?或者也许我这样做完全错了?
答案 0 :(得分:16)
C ++ 11已经有nested_exception。在Boostcon / C ++ 2012年的C ++ 03和C ++ 11中有一个关于例外的讨论。视频在youtube上:
答案 1 :(得分:4)
有很多额外的代码,但好处是它真的很容易在代码之间完全改变代码,因此可以预处理宏。
#define SUB_EXCEPTION(ClassName, BaseName) \
class ClassName : public BaseName\
{\
protected:\
\
ClassName(const ClassName& source) : BaseName(source) {};\
ClassName& operator= (const ClassName&) {};\
\
public:\
\
ClassName(const CString &message) : BaseName(message) {};\
ClassName(const CString &message, const BaseName &innerException) : BaseName(message, innerException) {};\
\
virtual CString GetExceptionName() const { return L"ClassName"; }\
\
virtual BaseName *Clone() const\
{\
ClassName *ex = new ClassName(this->Message);\
ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;\
return ex;\
}\
};
然后您可以通过以下方式定义各种实用程序异常:
SUB_EXCEPTION(IoException, Exception);
SUB_EXCEPTION(SerialPortException, IoException);
答案 2 :(得分:2)
请不要遵循boost :: exception方法。 Boost :: exception用于不同的用例 - 特别是当您想要收集调用堆栈上的精确异常上下文时,它非常有用。请考虑以下示例:
#include "TSTException.hpp"
struct DerivedException: TST::Exception {};
int main() try
{
try
{
try
{
try
{
throw std::runtime_error("initial exception");
}
catch(...)
{
throw TST::Exception("chaining without context info");
}
}
catch(...)
{
TST_THROW("hello world" << '!');
}
}
catch(...)
{
TST_THROW_EX(DerivedException, "another exception");
}
}
catch(const TST::Exception& ex)
{
cout << "diagnostics():\n" << ex;
}
catch(const std::exception& ex)
{
cout << "what(): " << ex.what() << endl;
}
我理解的“异常链接”解决方案应该产生类似于此的输出:
$ ./test
diagnostics():
Exception: another exception raised from [function: int main() at main.cpp:220]
Exception: hello world! raised from [function: int main() at main.cpp:215]
Exception: chaining without context info raised from [function: unknown_function at unknown_file:0]
Exception: initial exception
如您所见,存在彼此链接的异常,并且诊断输出包含具有上下文信息和可选堆栈跟踪的所有异常(此处未显示,因为它依赖于编译器/平台)。 使用新的C ++ 11错误处理功能(std :: current_exception或std :: nested_exception)可以自然地实现“异常链接”。这是TSTException.hpp的实现(请加上更多源代码):
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <exception>
#include <vector>
#include <string>
#include <memory>
#include <boost/current_function.hpp>
#include <boost/foreach.hpp>
using namespace std;
namespace TST
{
class Exception: virtual public std::exception
{
public:
class Context
{
public:
Context():
file_("unknown_file"),
line_(0),
function_("unknown_function")
{}
Context(const char* file, int line, const char* function):
file_(file? file: "unknown_file"),
line_(line),
function_(function? function: "unknown_function")
{}
const char* file() const { return file_; }
int line() const { return line_; }
const char* function() const { return function_; }
private:
const char* file_;
int line_;
const char* function_;
};
typedef std::vector<std::string> Stacktrace;
//...
Exception()
{
initStacktraceAndNestedException();
}
explicit Exception(const std::string& message, const Context&& context = Context()):
message_(message),
context_(context)
{
message.c_str();
initStacktraceAndNestedException();
}
~Exception() throw() {}
//...
void setContext(const Context& context) { context_ = context; }
void setMessage(const std::string& message) { (message_ = message).c_str(); }
const char* what() const throw () { return message_.c_str(); }
void diagnostics(std::ostream& os) const;
protected:
const Context& context() const { return context_; }
const std::exception_ptr& nested() const { return nested_; }
const std::shared_ptr<Stacktrace>& stacktrace() const { return stacktrace_; }
const std::string& message() const { return message_; }
private:
void initStacktraceAndNestedException();
void printStacktrace(std::ostream& os) const;
std::string message_;
Context context_;
std::shared_ptr<Stacktrace> stacktrace_;
std::exception_ptr nested_;
};
std::ostream& operator<<(std::ostream& os, const Exception& ex)
{
ex.diagnostics(os);
return os;
}
std::ostream& operator<<(std::ostream& os, const Exception::Context& context)
{
return os << "[function: " << context.function()
<< " at " << context.file() << ':' << context.line() << ']';
}
void Exception::diagnostics(std::ostream& os) const
{
os << "Exception: " << what() << " raised from " << context_ << '\n';
if (const bool haveNestedException = nested_ != std::exception_ptr())
{
try
{
std::rethrow_exception(nested_);
}
catch(const TST::Exception& ex)
{
if(stacktrace_ && !ex.stacktrace())//if nested exception doesn't have stacktrace then we print what we have here
printStacktrace(os);
os << ex;
}
catch(const std::exception& ex)
{
if(stacktrace_)
printStacktrace(os);
os << "Exception: " << ex.what() << '\n';
}
catch(...)
{
if(stacktrace_)
printStacktrace(os);
os << "Unknown exception\n";
}
}
else if(stacktrace_)
{
printStacktrace(os);
}
}
void Exception::printStacktrace(std::ostream& os) const
{
if(!stacktrace_)
{
os << "No stack trace\n";
return;
}
os << "Stack trace:";
BOOST_FOREACH(const auto& frame, *stacktrace_)
{
os << '\n' << frame;
}
os << '\n';
}
void Exception::initStacktraceAndNestedException()
{
nested_ = std::current_exception();
if(const bool haveNestedException = nested_ != std::exception_ptr())
{
try
{
throw;
}
catch(const TST::Exception& ex)
{
if(ex.stacktrace())
{
stacktrace_ = ex.stacktrace();
return;
}
}
catch(...) {}
}
/*TODO: setStacktrace(...); */
}
}//namespace TST
#ifdef TST_THROW_EX_WITH_CONTEXT
#error "TST_THROW_EX_WITH_CONTEXT is already defined. Consider changing its name"
#endif /*TST_THROW_EX_WITH_CONTEXT*/
#define TST_THROW_EX_WITH_CONTEXT( \
CTX_FILE, CTX_LINE, CTX_FUNCTION, EXCEPTION, MESSAGE) \
do \
{ \
EXCEPTION newEx; \
{ \
std::ostringstream strm; \
strm << MESSAGE; \
newEx.setMessage(strm.str()); \
} \
newEx.setContext( \
TST::Exception::Context( \
CTX_FILE, CTX_LINE, CTX_FUNCTION)); \
throw newEx; \
} \
while(0)
#ifdef TST_THROW_EX
#error "TST_THROW_EX is already defined. Consider changing its name"
#endif /*TST_THROW_EX*/
#define TST_THROW_EX(EXCEPTION, MESSAGE) \
TST_THROW_EX_WITH_CONTEXT(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, EXCEPTION, MESSAGE)
#ifdef TST_THROW
#error "TST_THROW is already defined. Consider changing its name"
#endif /*TST_THROW*/
#define TST_THROW(MESSAGE) \
TST_THROW_EX(TST::Exception, MESSAGE)
我使用编译器支持部分C ++ 11(gcc 4.4.7),所以你可以在这里看到一些旧式的代码。仅供参考,您可以使用以下编译参数来构建此示例(-rdynamic用于堆栈跟踪):
g ++ main.cpp TSTException.hpp -rdynamic -o test -std = c ++ 0x
答案 3 :(得分:1)
几年前我写了这个:Unchaining Chained Exceptions in C++
基本上,异常并不是彼此嵌套的,因为它很难捕获原始异常,但是另一种机制会跟踪异常在访问其捕获点时所访问的所有函数。
可以在Bitbucket上的Imebra库here和here中找到它的重访版本。
现在我会通过一些改进重写(例如使用本地线程存储来保持堆栈跟踪)。
使用这种方法可以捕获抛出的原始异常,但仍然具有堆栈跟踪以及异常访问的函数在返回catch语句时可能添加的其他信息。