Catching exceptions from a constructor means that

2019-01-20 14:33发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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...