Question

I have a class that can throw an exception in its constructor. How can I declare an instance of that class in a try/catch block, while still making it available in the right scope?

try { MyClass lMyObject; }
catch (const std::exception& e) { /* Handle constructor exception */ }

lMyObject.DoSomething(); // lMyObject not in scope!

Is there an alternative way to accomplish this, while respecting the RAII idiom?

I'd prefer not to use an init() method for two-phased construction. The only other thing I could come up with was:

MyClass* lMyObject;

try { lMyObject = new MyClass(); }
catch (const std::exception& e) { /* Handle constructor exception */ }

std::shared_ptr<MyClass> lMyObjectPtr(lMyObject);
lMyObjectPtr->DoSomething();

Works OK, but I'm not happy with the raw pointer in scope and pointer indirection. Is this just another C++ wart?

Était-ce utile?

La solution

If a constructor throws that means the object failed to initialize and hence it failed to start its existence.

MyClass* lMyObject;
try { lMyObject = new MyClass(); }
catch (std::exception e) { /* Handle constructor exception */ }

In the above if the constructor throws an exception, lMyObject is left uninitialized, in other words, the pointer contains an indeterminate value.

See classic Constructor Failures for a detailed explanation:

We might summarize the C++ constructor model as follows:

Either:

(a) The constructor returns normally by reaching its end or a return statement, and the object exists.

Or:

(b) The constructor exits by emitting an exception, and the object not only does not now exist, but never existed.

There are no other possibilities.

Autres conseils

The best way of writing your code is this:-

MyClass lMyObject; 
lMyObject.DoSomething(); 

No trys, catches, or pointers.

If the constructor throws, then DoSomething can't get called. Which is right: If the constructor threw, then object was never constructed.

And, importantly, don't catch (or even catch/rethrow) exceptions unless you have something constructive to do with them. Let exceptions do their job and ripple up until something that knows how to handle them can do its job.

Constructors are for putting an object into a consistent state and establishing class invariants. Allowing an exception to escape a constructor means that something in that constructor has failed to complete, so now the object is in some unknown weird state (which may include resource leaks now too). Since the object's constructor has not completed, the compiler will not cause it's destructor to be invoked either. Perhaps what you're looking for is to catch the exception inside the constructor. Assuming it's not rethrown, this would cause the constructor to complete execution, and the object is now fully-formed.

You don't need to use shared_ptr, use unique_ptr:

std::unique_ptr<MyClass> pMyObject;
try { pMyObject.reset(new MyClass()); }
catch (std::exception &e) { /* Handle constructor exception */ throw; }
MyClass &lMyObject = *pMyObject;

lMyObject.DoSomething();

Obviously, it's your responsibility to ensure that the program does not fall through the catch block without either initialising pMyObject, or exiting the function (e.g. via return or throw).

If available, you can use Boost.Optional to avoid using heap memory:

boost::optional<MyClass> oMyObject;
try { oMyObject.reset(MyClass()); }
catch (std::exception &e) { /* Handle constructor exception */ throw; }
MyClass &lMyObject = *oMyObject;

lMyObject.DoSomething();

You could set up MyClass's copy constructor to accept garbage input, thereby effectively declaring a pointer with your declaration of the object. Then you could manually call the default constructor within the try block:

MyClass lMyObject(null); // calls copy constructor
try {
    new (lMyObject) MyClass(); // calls normal constructor
}
catch (const std::exception& e) { /* Handle constructor exception */ }

lMyObject.DoSomething();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top