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;
}
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?
Yes, this is guaranteed by the standard.
To start, std::exception
does not own what
– std::runtime_error
does. std::runtime_error
's constructors are defined thusly ([runtime.error]p2-5):
runtime_error(const string& what_arg);
Effects: Constructs an object of class
runtime_error
.
Postcondition:strcmp(what(), what_arg.c_str()) == 0
.runtime_error(const char* what_arg);
Effects: Constructs an object of class
runtime_error
.
Postcondition:strcmp(what(), what_arg) == 0
.
So, it must store a copy of what_arg
internally, as there are no requirements about the lifetime of the value passed in.
Next there's [exception]p2:
Each standard library class
T
that derives from classexception
shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception. These member functions shall meet the following postcondition: If two objectslhs
andrhs
both have dynamic typeT
andlhs
is a copy ofrhs
, thenstrcmp(lhs.what(), rhs.what())
shall equal0
.
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_error
must retain the value you pass for what_arg
internally 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 to std::runtime_error
, not down to std::exception
! (More information about the rationales and requirements regarding what
's storage can be found in this very interesting answer by @HowardHinnant: move constructor for std::runtime_error)
std::system_error
inherits from std::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).
I'm assuming this would enable a
std::exception
copy ofMyException
to be able to return a validwhat()
.
No! When you make a std::exception
copy of MyException
, you are slicing the object down to a less derived type than where what
's value is physically stored. If you must make a copy of your exception, the least derived type you can use is std::runtime_error
. (You can always safely make a std::exception
reference to a MyException
, of course.) To put it another way, it is never possible to get a meaningful string from a std::exception
object's what()
.
This code has the behavior you want, portably:
#include <cstdio>
#include <stdexcept>
#include <system_error>
#include <string>
class MyException : public std::system_error {
public:
MyException(int errorValue, std::error_category const& category)
: std::system_error(
errorValue, category,
"MyException: " + std::to_string(errorValue)
)
{ }
};
int main() {
std::runtime_error ex;
try {
throw MyException(4, system_category());
} catch(MyException const& e) {
ex = e;
}
std::printf("what= %s", ex.what());
}
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.