I ran into some problems with circular dependencies when I made my exceptions derive from SuString, but by splitting the header I got around these.
The hardest part was remembering how C++ exceptions work. A year of programming mostly in Java and already the C++ details are fading!
I remembered something in Effective C++ but it turned out to be in More Effective C++.
The recommended style is:
throw MyException(...)At first glance this seems wrong because you're throwing a temporary value, but C++ copies thrown exceptions. And catching by reference avoids a second copy that's done if you catch by value.
catch (const MyException& e)
Of course, this recommendation assumes no garbage collection. With garbage collection, like in Suneido, it's reasonable to just throw and catch pointers, which is what I ended up doing. (Before changing to pointers I ran into problems because I passed the caught exception to the Suneido catch code, forgetting that the caught value is a temporary on the stack.)
It was a good reminder of the extra complexity in C++. In Java you don't have to worry about whether you have a value or a pointer or a reference and whether it's on the stack or the heap. You do have the difference between primitive types and reference types, but especially with auto-boxing and un-boxing it's pretty transparent. And in any case, it doesn't lead to crashes like forgetting a C++ value is on the stack.
Since exceptions are a sub-class of strings, all the existing code that assumes strings works fine.
I added two methods: exception.As(newString) to re-throw an exception with a different string. And exception.Callstack() to retrieve the call stack from an exception (e.g. to log it).
Next, I'll have to go through our libraries and make sure that any re-throws are done properly to preserve the original source of the exception, and that any logging uses the callstack from the exception.