I have an Exception class as follows:
class ExtensionExceptionType;
class Object;
class Exception
{
public:
explicit Exception () { }
Exception( const std::string &reason ) { PyErr_SetString( _Exc_RuntimeError(), reason.c_str() ); }
Exception( PyObject* exception, const std::string &reason ) { PyErr_SetString( exception, reason.c_str() ); }
Exception( PyObject* exception, Object& reason );
Exception( ExtensionExceptionType& exception, const std::string& reason );
Exception( ExtensionExceptionType& exception, Object& reason );
void clear() { PyErr_Clear(); } // clear the error -- technically but not philosophically const
static Object err_type();
static Object err_value();
static Object err_trace();
static Object err_stats( uint32_t i ); // 0 1 2 for {type, value, trace}
static void wrap( int condition ) {
if( condition == -1 )
throw Exception{};
}
};
// Abstract
class StandardError : public Exception { protected: explicit StandardError() {} };
class LookupError : public StandardError { protected: explicit LookupError() {} };
class ArithmeticError : public StandardError { protected: explicit ArithmeticError() {} };
class EnvironmentError : public StandardError { protected: explicit EnvironmentError() {} };
// Concrete (π)
// e.g.
// class TypeError: public StandardError
// {
// public:
// TypeError (const std::string& reason)
// : StandardError()
// {
// PyErr_SetString( Py::_Exc_TypeError(),reason.c_str() );
// }
// };
#define CONCRETE( CLASS, BASE ) \
class CLASS: public BASE \
{ \
public: \
CLASS (const std::string& reason) \
{ \
std::cout << "(Exception.hxx) " #CLASS " from PyCXX (" << reason.c_str() << ") \n"; \
PyErr_SetString( _Exc_##CLASS(), reason.c_str() ); \
} \
};
// it appears that these classes are only for manually RAISING Python errors
// i.e. Raising an exception in the Python runtime
// because if I type something in to the Python console, I can make (e.g.) a KeyError occur, but these classes don't get hit.
CONCRETE( TypeError, StandardError )
CONCRETE( IndexError, LookupError )
CONCRETE( AttributeError, StandardError )
CONCRETE( NameError, StandardError )
CONCRETE( RuntimeError, StandardError )
CONCRETE( NotImplementedError, StandardError )
CONCRETE( SystemError, StandardError )
CONCRETE( KeyError, LookupError )
CONCRETE( ValueError, StandardError )
CONCRETE( OverflowError, ArithmeticError )
CONCRETE( ZeroDivisionError, ArithmeticError )
CONCRETE( FloatingPointError, ArithmeticError )
CONCRETE( MemoryError, StandardError )
CONCRETE( SystemExit, StandardError )
I've just added:
static void wrap( int condition ) {
if( condition == -1 )
throw Exception{};
}
... because there were many occasions elsewhere of...
if( SomePythonFunc(...) == -1 ) throw Exception{};
... which have been tidied up into:
Exception.wrap( SomePythonFunc(...) ); // much nicer, I think
However, there also cases of:
if( SomePythonFunc(...) == -1 ) throw TypeError{ "foo" };
... and I can't see how to perform the equivalent wrapping.
i.e. write:
TypeError.wrap( SomePythonFunc(...), "foo" );
As TypeError : Exception, and Exception::wrap is public, I can just create an optional second parameter for wrap:
static void wrap( int condition, string err="default-err" ) {
if( condition == -1 )
throw FinalClassConstructor{ err }; // <-- how to do this?
}
... but how do I then invoke the constructor for the final class whose ::wrap just got hit?
What you are trying to do defies multiple SOLID principles. You are also making your base class tightly coupled(!) with it's descendants. You definitely don't want that. It should have no idea who inherits in.
To do it right, override the Wrap function within the child classes where they throw themselves basically. In your code you will go StandardError.Wrap(...
But honestly I would just leave the exceptions in the code.
(WHATEVER CONDITION -> Throw exception) is perfectly fine in the code and is way more readable than exception within another exception with a static method.
This is the case when you shouldn't refactor it any more.