I\'d like to implement an Exception class in C++ that mimics the one from .NET framework (and Java has something similar too), for the following purposes:
E
Please don't follow boost::exception approach. Boost::exception is for different use case - in particular it's usefull when you want to collect precise exception context scatered over call stack. Consider the following example:
#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;
}
The "exception chaining" solution as I understand it should produce output similar to this:
$ ./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
As you see there are exceptions chained to each other and diagnostic output contains all exceptions with context information and optional stack trace (not shown here, because it's compiler/platform dependent). "Exception chaining" can be naturally achieved using new C++11 error handling features (std::current_exception or std::nested_exception). Here is implementation of TSTException.hpp (please bear with more source code):
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 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() 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_;
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)
I use compiler with partial C++11 support (gcc 4.4.7) so you can see some old style peaces of code here. Just for reference you can use the following compilation parameters to build this example (-rdynamic is for stack trace):
g++ main.cpp TSTException.hpp -rdynamic -o test -std=c++0x