I'm deriving my own exception, call it MyException, from std::system_error and have overridden what() to calculate and return my message. MyException's initializer list doesn't call the system_error constructor override that takes a message.
If I catch a MyException and copy it to a std::exception the result of calling what() on the std::exception is nullptr. This makes sense.
My question is, if I do use the constructor of system_exception that takes a message when initializing MyException, is it specified that system_error will take a copy of the message and own it and free it?
I'm assuming this would enable a std::exception copy of MyException to be able to return a valid what(). Although I would take a performance hit in that the 'what' needs calculating every time a new one of MyExceptions is created; I can't lazily calculate it only when what() is first called.
I'm slightly worried about the ownership of the 'what' string as what() returns a char* and not a const std::string&.
The code is something like this (I haven't compiled this):
class MyException : public std::system_error
{
std::string what_;
public:
MyException(int errorValue, const std::error_category& category)
: std::system_error(errorValue, category)
{}
char* what() const
{
what_ = "MyException: " + to_string(code().value());
return what_.c_str();
}
};
int main()
{
std::exception ex;
try
{
throw MyException(4, system_category());
}
catch( const MyException& e )
{
ex = e;
}
printf("what= %s", ex.what());
return 1;
}
Yes, this is guaranteed by the standard.
To start,
std::exceptiondoes not ownwhat–std::runtime_errordoes.std::runtime_error's constructors are defined thusly ([runtime.error]p2-5):So, it must store a copy of
what_arginternally, as there are no requirements about the lifetime of the value passed in.Next there's [exception]p2:
So, there must be a copy constructor, it must never throw, and copies must maintain the same return value for
what(). Likewise for the copy-assignment operator.Putting this all together, we can surmise that
std::runtime_errormust retain the value you pass forwhat_arginternally in a reference counted string (to avoid exceptions from allocations when copying), and the value will persist regardless of copying and/or slicing – but only down tostd::runtime_error, not down tostd::exception! (More information about the rationales and requirements regardingwhat's storage can be found in this very interesting answer by @HowardHinnant: move constructor for std::runtime_error)std::system_errorinherits fromstd::runtime_error, so all the same holds true for it and any type deriving from it (as long as the derived type maintains the non-throwing copy constructor invariant).No! When you make a
std::exceptioncopy ofMyException, you are slicing the object down to a less derived type than wherewhat's value is physically stored. If you must make a copy of your exception, the least derived type you can use isstd::runtime_error. (You can always safely make astd::exceptionreference to aMyException, of course.) To put it another way, it is never possible to get a meaningful string from astd::exceptionobject'swhat().This code has the behavior you want, portably:
I would say that it's poor form to write an exception constructor that allocates (for obvious reasons), but given that every current standard library implementation that I'm aware of uses short-string optimization for
std::basic_string<>, this is extremely unlikely to ever be an issue in practice.