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.
Your question is related to understanding the lifecycle of an exception. That question is discussed in posts here and here and may be helpful.
You could guarantee that the lifetime of your exception is extended using a smart pointer. I'm not sure what the performance implications would be, but you could probably use this to hang on to your own extension of the std::system_error
and avoid the copy construction altogether. (Actually, I don't guarantee the copy construction would be avoided. Creation of the smart pointer may or may not copy the exception, it seems. But it would copy your exception, which should do the right thing if you provide the copy constructor that you should provide.) Your main function would end up looking more like this.
#include <exception> // std::exception_ptr
int main()
{
std::exception_ptr p;
try
{
throw MyException(4, system_category());
}
catch( const MyException& e )
{
p = std::current_exception();
}
try
{
std::rethrow_exception(p);
}
catch (const std::exception& e)
{
printf("what= %s", e.what());
}
return 1;
}
This is basically just a rewrite of the example of using an exception pointer that I read about on cplusplus.com here, but I've used your exception class rather than a standard exception like std::logic_error
.
As to your original question, it seems that it is difficult to make hard guarantees. I did find the following statement in the documentation of the assignment operator for exception as applies to C++11. In C++98 not even this guarantee is provided.
Every exception within the C++ standard library (including this) has, at least, a copy assignment operator overload that preserves the string representation returned by member what when the dynamic types match.
However, the dynamic type of std::system_error
would not match the dynamic type of std::exception
in your case, so I don't think it would be guaranteed to work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With