There isn't a lot that an exception needs to convey: what went wrong and why it was wrong. So that part of your design is good.
However, you're using std::string
and std::stringstream
. This is generally considered bad because the constructor for std::string
can throw an exception when there is no memory (and std::stringstream
calls std::string
's constructor).
So Conan attempts to cast a spell, this causes an exception because Conan isn't a wizard, you go to create the exception but find out there's no available memory for your std::string
to use, so creating the exception throws an exception. At this point you have two problems that need to be dealt with (Conan's attempted spell and no available memory) and the program has no way of knowing which should be dealt with first. C++ solves this problem with a simple solution: the program is terminated immediately. Of course you don't get a whole lot of information out of the program, and nothing is cleaned up (files aren't flushed to disk, for instance).
The std::exception
class uses char*
s for this reason. And if you're going to go that route, then it makes sense to go the full Monty and inherit (publicly) from std::exception
.
As for converting an int
to char*
without std::stringstream
, you can do:
#include <limits>
#include <cstdio>
char as_string[std::numeric_limits<int>::digits10 + 1];
if (std::sprintf(as_string, "%d", x) >= 0) {
// as_string has the converted char* string here ...
}
I'm not sure what you get out of this. The part of the program catching the exception will be responsible for deciding what to do with the line number. If it chooses to display the line number, it can be expected to do the conversion. If it wishes to do something else, you will have done unnecessary work. If you were dealing with something other than line numbers (errno
codes, for instance) there might even be a chance that the exception handler would need to convert the string back to an int
(to pass to perror()
, for instance).
There are two other issues:
- There is no need for setMessage().
throw Exception("what went wrong")
is far more common than
Exception ex;
ex.setMessage("what went wrong");
throw ex;
And if you were to go with this second version you wouldn't be throwing ex
per se. Instead you'd be throwing a copy of ex
(see next point), so you'd need a copy constructor.
- There's no need for the virtual destructor.
As noted, you won't be manipulating exceptions via pointers to the base class anyway. But you also don't delete
exceptions when you're done with them (which is when the virtual destructor would be called). C++ requires special mechanisms for exceptions. throw
allocates the object somewhere special (that is, some place it won't be destroyed while the stack is unwound) so that the object will be around inside your catch
block. This is the reason throw ex
would create a copy of ex
and throw the copy -- ex
will be gone by the time you get to a catch
block. When you're done handling the exception the object gets destroyed automatically.