RAII approach to catching constructor exceptions
Asked Answered
M

5

10

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?

Meanwhile answered 5/2, 2013 at 14:52 Comment(7)
Move the DoSomething() method call to the try block?Struck
Never catch exceptions by copy. Stick with constant references.Bordure
In the second example, if the constructor threw, lMyObject is an uninitialized pointer.Sadoff
@VladLazarenko: Thanks for pointing out the catch-by-valueMeanwhile
possible duplicate of Destructor not invoked when an exception is thrown in the constructorEndophyte
You could use a factory function that handles the exception, then it's only one line for the caller.Hackneyed
@VladLazarenko: in C++11 a reference to non-const can be better in case one wants to move an exception.Bill
B
5

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.

Bill answered 5/2, 2013 at 15:2 Comment(0)
N
2

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.

Nympho answered 5/2, 2013 at 15:43 Comment(0)
S
1

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.

Sinkhole answered 5/2, 2013 at 15:34 Comment(0)
N
0

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();
Neutrality answered 5/2, 2013 at 15:7 Comment(10)
@MaximYegorushkin that depends entirely on what /* Handle constructor exception */ does.Neutrality
A good test for your advice is to replace /* Handle constructor exception */ with an empty string. Your advice fails this test.Bill
@MaximYegorushkin also, it doesn't dereference a NULL pointer; per 20.7.1.2.4p1 get() != nullptr is a prerequisite on operator*, so it's actually undefined behaviour.Neutrality
@MaximYegorushkin doing nothing is not handling an exception; it's ignoring it.Neutrality
once control flow enters a catch block the exception is considered handled.Bill
@MaximYegorushkin only in the sense that it is the currently handled exception; in terms of program logic it's being ignored.Neutrality
@MaximYegorushkin I've added throw; statements above to prevent any undefined behaviour.Neutrality
Try again replacing /* Handle constructor exception */ with an empty string and see what happens. Basically, you are asking the reader to figure out a solution for you.Bill
@MaximYegorushkin The exception is rethrown, ensuring that no constraints are violated. The reader already has a known method for handling exceptions; it's just a question of providing a pattern to enable its safe use.Neutrality
@MaximYegorushkin btw, the standard uses "handle" in my sense in 15.1p8: code that must be executed because of an exception yet cannot completely handle the exception...Neutrality
B
-1

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();
Blandishments answered 5/2, 2013 at 15:23 Comment(2)
It calls constructor of MyClass twice, destructor once.Bill
Right - for this pattern to work, you would have to write your copy constructor such that it didn't allocate any resources. It's a bit hackish.Blandishments

© 2022 - 2024 — McMap. All rights reserved.