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?

sourcenouveau
  • 29,356
  • 35
  • 146
  • 243

5 Answers5

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.

Community
  • 1
  • 1
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
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.

Roddy
  • 66,617
  • 42
  • 165
  • 277
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.

Andre Kostur
  • 770
  • 1
  • 6
  • 15
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();
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • @MaximYegorushkin that depends entirely on what `/* Handle constructor exception */` does. – ecatmur Feb 05 '13 at 15:12
  • A good test for your advice is to replace `/* Handle constructor exception */` with an empty string. Your advice fails this test. – Maxim Egorushkin Feb 05 '13 at 15:14
  • @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. – ecatmur Feb 05 '13 at 15:16
  • @MaximYegorushkin doing nothing is *not* handling an exception; it's ignoring it. – ecatmur Feb 05 '13 at 15:17
  • once control flow enters a `catch` block the exception is considered handled. – Maxim Egorushkin Feb 05 '13 at 15:18
  • @MaximYegorushkin only in the sense that it is the *currently handled exception*; in terms of program logic it's being ignored. – ecatmur Feb 05 '13 at 15:25
  • @MaximYegorushkin I've added `throw;` statements above to prevent any undefined behaviour. – ecatmur Feb 05 '13 at 15:26
  • 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. – Maxim Egorushkin Feb 05 '13 at 15:28
  • @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. – ecatmur Feb 05 '13 at 15:35
  • @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...* – ecatmur Feb 08 '13 at 15:28
-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();
Community
  • 1
  • 1
John Walthour
  • 786
  • 2
  • 9
  • 23