I have a class whose constructor may throw an exception. Here’s some code that will catch the exception:
try {
MyClass instance(3, 4, 5);
}
catch (MyClassException& ex) {
cerr << "There was an error creating the MyClass." << endl;
return 1;
}
But of course no code after the try/catch can see instance
because it’s now out of scope. One way to resolve this would be to declare and define instance
separately:
MyClass instance;
try {
MyClass instance(3, 4, 5);
}
...
except that my class doesn’t have the appropriate zero-argument constructor. In fact, this case right here is the only one in which such a constructor would even make sense: the MyClass
object is intended to be immutable, in the sense that none of its data members change after construction. If I were to add a zero-argument constructor I’d need to introduce some instance variable like is_initialized_
and then have every method check to make sure that that variable is true
before proceeding. That seems like far too much verbosity for such a simple pattern.
What is the idiomatic way to deal with this kind of thing? Do I need to suck it up and allow instances of my class to be declared before they’re initialized?
You should be doing everything you need to do inside the try
block:
try {
MyClass instance(3, 4, 5);
// Use instance here
}
catch (MyClassException& ex) {
cerr << "There was an error creating the MyClass." << endl;
return 1;
}
After all, it is only within the try
block that instance
has been successfully created and so can be used.
I do wonder whether your catch
block is really handling the exception. If you can't do anything to resolve the situation, you should be letting it propagate.
You could use a generic helper function that catches exceptions and the future std::optional
(or boost::optional
) to signal the successful or failed creation of the instance:
template< typename T, typename... Args >
std::optional< T > try_make( Args&&... args )
{
try {
return T{ std::forward< Args >( args )... };
}
catch( ... ) {
return {};
}
}
Using basically this:
auto instance = try_make< MyClass >(3, 4, 5);
Where instance
is now an optional<MyClass>
. To test the result and separate availablity of the instance from the error case is also simple:
if( auto instance = try_make< MyClass >( 3, 4, 5 ) ) {
// use *instance, but this code is *not* in the try/catch block!
}
else {
// creating the instance failed
}
Of course the exception information will be lost this way, but you could go for a less generic function and add some logging in the catch-block depending on your needs.
Dynamically allocate the instance using new
:
std::unique_ptr<MyClass> instance;
try
{
instance.reset(new MyClass(3, 4, 5));
}
catch (const MyClassException& ex)
{
std::cerr << "There was an error creating the MyClass." << std::endl;
return 1;
}
// use instance as needed...