Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Sutter's Mill: Constructor Failures (or, The Objects That Never Were)


November 2000/Sutter's Mill


Function try blocks were added to C++ to slightly enlarge the scope of exceptions that a function can catch. This column will show:

  • what object construction, and construction failure, mean in C++; and
  • that function try blocks are useful to translate (not suppress) an exception thrown from a base or member subobject constructor — and that's pretty much it.

For convenience, throughout this article "member" means "nonstatic class data member" unless otherwise noted.

Function Try Blocks

Consider the following class:

// Example 1
//
class C : private A
{
  // ...

  B b_;
};

In the C constructor, say you want to catch an exception thrown from the constructor of a base subobject (such as A) or a member object (such as b_). That's what function try blocks are for:

// Example 1(a): Constructor function
// try block
//
C::C()
try
  : A ( /*...*/ ) // optional
  , b_( /*...*/ ) // initialization
                  // list
{
}
catch( ... )
{
  //  We get here if either A::A() or
  //  B::B() throws.

  //  If A::A() succeeds and then
  //  B::B() throws, the language
  //  guarantees that A::~A() will
  //  be called to destroy the
  //  already-created A base
  //  subobject before control
  //  reaches this catch block.
}

The more interesting question, though, is: Why would you want to do this? That question brings us to the first of two key points in this column.

Object Lifetimes, and What a Constructor Exception Means

In a moment, we'll consider the question of whether the above C constructor can, or should, absorb an A or B constructor exception, and emit no exception at all. But before we can answer that question correctly, we need to make sure we fully understand object lifetimes [1] and what it means for a constructor to throw an exception.

Take a simple example:

{
  Parrot parrot;
}

In the above code, when does the object's lifetime begin? When does it end? Outside the object's lifetime, what is the status of the object? Finally, what would it mean if the constructor threw an exception? Take a moment to think about these questions before reading on.

Let's take these items one at a time:

Q: When does an object's lifetime begin?

A: When its constructor completes successfully and returns normally. That is, when control reaches the end of the constructor body or an earlier return statement.

Q: When does an object's lifetime end?

A: When its destructor begins. That is, when control reaches the beginning of the destructor body.

Q: What is the state of the object after its lifetime has ended?

A: As a well known software guru [2] once put it, speaking about a similar code fragment and anthropomorphically referring to the local object as a "he":

"He's not pining! He's passed on! This Parrot is no more! He has ceased to be! He's expired and gone to meet his maker! He's a stiff! Bereft of life, he rests in peace! If you hadn't nailed him to the perch he'd be pushing up the daisies! His metabolic processes are now history! He's off the twig! He's kicked the bucket, he's shuffled off his mortal coil, run down the curtain and joined the bleedin' choir invisible! This is an ex-Parrot!"

— Dr. M. Python, BMath, MASc, PhD (CompSci) [3]

Kidding aside, the important point here is that the state of the object before its lifetime begins is exactly the same as after its lifetime ends — there is no object, period. This observation brings us to the key question:

Q: What does emitting an exception from a constructor mean?

A: It means that construction has failed, the object never existed, its lifetime never began. Indeed, the only way to report the failure of construction — that is, the inability to correctly build a functioning object of the given type — is to throw an exception. (Yes, there is a now-obsolete programming convention that said, "if you get into trouble just set a status flag to ‘bad' and let the caller check it via an IsOK function." I'll comment on that presently.)

In biological terms, conception took place — the constructor began — but despite best efforts it was followed by a miscarriage — the constructor never ran to term(ination).

Incidentally, this is why a destructor will never be called if the constructor didn't succeed — there's nothing to destroy. "It cannot die, for it never lived." Note that this makes the phrase "an object whose constructor threw an exception" really an oxymoron. Such a thing is even less than an ex-object... it never lived, never was, never breathed its first. It is a non-object.

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. Armed with this information, we can now fairly easily tackle the question about absorbing exceptions.

I Can't Keep No Caught Exceptions [4]

In Example 1, if the A or B constructor throws an exception, is it possible for the C constructor to absorb the exception, and emit no exception at all? Well, if we didn't consider the object lifetime rules, we might have tried something like the following:

// Example 2(a): Absorbing exceptions?
//
C::C()
try
  : A ( /*...*/ ) // optional
  , b_( /*...*/ ) // initialization
                  // list
{
}
catch( ... )
{
  // ?
}

How will the try block handler exit? Consider:

  • The handler cannot simply "return;" because that's illegal.
  • If the handler says just "throw;" then it will rethrow whatever exception A::A() or B::B() originally emitted.
  • If the handler throws some other exception, that exception is emitted instead of whatever the base or member subobject constructor originally threw.
  • What's less obvious, but clearly stated in the C++ Standard, is that if the handler does not exit by throwing an exception (either rethrowing the original exception, or throwing something new), and control reaches the end of the catch block of a constructor or destructor, then the original exception is automatically rethrown.

Think about what this means: A constructor or destructor function try block's handler code must finish by emitting some exception. There's no other way. As long as you don't try to violate exception specifications, the language doesn't care what exception it is that gets emitted — it can be the original one, or some other translated exception — but an exception there must be! It is impossible to keep any exceptions thrown by base or member subobject constructors from causing some exception to leak beyond their containing constructors.

In fewer words, it means that:

In C++, if construction of any base or member subobject fails, the whole object's construction must fail.

This is no different from saying "there is no way for a human to exist (i.e., be born alive) if any of its vital organs (e.g., heart, liver) were never formed." If you tried to continue by keeping at least those parts you were able to make, the result may be a hunk of miscellaneous flesh — an arm here, an ear there — but it was never a human being. There is no such thing as an "optional" base or member (held by value or reference) subobject.

A constructor cannot possibly recover and do something sensible after one of its base or member subobjects' constructors throws. It cannot even put its own object into a "construction failed" state. Its object is not constructed, it never will be constructed no matter what Frankensteinian efforts the handler attempts in order to breathe life into the non-object, and whatever destruction can be done has already been done automatically by the language — and that includes all base and member subobjects.

What if your class can honestly have a sensible "construction partially failed" state — i.e., it really does have some "optional" members that aren't strictly required and the object can limp along without them, possibly with reduced functionality? Then use the Pimpl idiom (described in Exceptional C++ Items 27-30) to hold the possibly-bad parts of the object at arm's length [5]. For similar reasoning, see Exceptional C++ Items 31-34 about abuses of inheritance; incidentally, this "optional parts of an object" idea is another great reason to use delegation instead of inheritance whenever possible, because base subobjects can never be made optional because you can't put base subobjects into a Pimpl.

I've always had a love/hate relationship with exceptions, and have sometimes taken a dim view of them. Even so, I've always had to agree that exceptions are the right way to signal constructor failures given that constructors cannot report errors via return codes (ditto for most operators). I have found the "if a constructor encounters an error, set a status bit and let the user call IsOK to see if construction actually worked" method to be outdated, dangerous, tedious, and in no way better than throwing an exception.

A Step toward Morality

Incidentally, this also means that the only (repeat only) possible use for a constructor function try block is to translate an exception thrown from a base or member subobject. That's Moral #1. Next, Moral #2 says that destructor function try blocks are entirely usele—

"—But wait!" I hear someone interrupting from the middle of the room, "I don't agree with Moral #1. I can think of another possible use for constructor function try blocks, namely to free resources allocated in the initializer list or in the constructor body!"

Sorry, no. After all, remember that once you get into your constructor try block's handler, any local variables in the constructor body are also already out of scope, and you are guaranteed that no base subobjects or member objects exist any more, period. You can't even refer to their names. Either the parts of your object were never constructed, or those that were constructed have already been destroyed. So you can't be cleaning up anything that relies on referring to a base or member of the class (and anyway, that's what the base and member destructors are for, right?).

Aside: Why Does C++ Do It That Way?

To see why it's good that C++ does it this way, let's put that restriction aside for the moment and imagine, just imagine, that C++ did let you mention member names in constructor try block handlers. Then imagine the following case, and try to decide: Should the handler delete t_ or z_? (Again, ignore for the moment that in real C++ it can't even refer to t_ or z_.)

// Example 2(b): Very Buggy Class
//
class X : Y
{
  T* t_;
  Z* z_;
public:
  X()
  try
    : Y(1)
    , t_( new
        T( static_cast<Y*>(this) )
    , z_( new
        Z(static_cast<Y*>(this), t_))
  {
    /*...*/
  }
  catch(...)  // Y::Y or T::T or Z::Z
              // or X::X's body has
              // thrown
  {
    // Q: should I delete t_ or z_?
    // (note: not legal C++)
  }
};

The first problem is that we cannot possibly know whether t_ or z_ were ever allocated. Therefore deleting either one might not be safe.

Second, even if we did know that we had reached one of the allocations, we probably can't destroy *t_ or *z_ because they refer to a Y (and possibly a T) that no longer exists and they may try to use that Y (and possibly T). Incidentally, this means that not only can't we destroy *t_ or *z_, but they can never be destroyed by anyone.

If that didn't just sober you up, it should have. I have seen people write code similar in spirit to the above, never imagining that they were creating objects that, should the wrong things happen, could never be destroyed! The good news is that there's a simple way to avoid the problem: these difficulties would largely go away if the T* members were instead held by auto_ptrs or similar manager objects.

Finally, if Y::~Y can throw, it is not possible to reliably create an X object at any time! If you haven't been sobered yet, this should definitely do it. If Y::~Y can throw, even writing "X x;" is fraught with peril. This reinforces the dictum that destructors must never be allowed to emit an exception under any circumstances, and writing a destructor that could emit an exception is simply an error. Destruction and emitting exceptions don't mix.

All right, enough about that. The above side discussion was just to get a better understanding of why it's good that the rules are as they are. After all, in real C++ you can't even refer to t_ or z_ inside the handler in the first place. I've refrained from quoting standardese so far, so here's your dose: from the C++ Standard, clause 15.3, paragraph 10:

Referring to any non-static member or base class of an object in the handler for a function try block of a constructor or destructor for that object results in undefined behavior.

Morals about Function Try Blocks

Therefore the status quo can be summarized as follows:

Moral #1: Constructor function try block handlers are good only for translating an exception thrown from a base or member subobject constructor (and maybe to do related logging or some other side effects in response to such failures). They are not useful for any other purpose.

Moral #2: Destructor function try blocks have little or no practical use, because destructors should never emit an exception [7]. Thus there should never be anything for a destructor function try block to detect that couldn't be detected with a normal try block — and even if there were something to detect because of evil code (i.e., a member subobject whose destructor could throw), the handler would not be very useful for doing anything about it because it couldn't suppress the exception. The best it could do is would be to log something, or otherwise complain.

Moral #3: All other function try blocks have no practical use. A regular function try block can't catch anything that a regular try block within the function couldn't catch just as well.

Morals about Safe Coding

Moral #4: Always perform unmanaged resource acquisition in the constructor body, never in initializer lists. In other words, either use "resource acquisition is initialization" [8] (thereby avoiding unmanaged resources entirely) or else perform the resource acquisition in the constructor body. For example, building on Example 2(b), say T was char and t_ was a plain old char* that was new[]ed in the initializer-list; then there would be no way to call delete[] on it, in the handler or anywhere else. The fix would be to instead either wrap the dynamically allocated memory resource (e.g., change char* to string) or new[] it in the constructor body where it can be safely cleaned up using a local try block or otherwise.

Moral #5: Always clean up unmanaged resource acquisition in local try block handlers within the constructor or destructor body, never in constructor or destructor function try block handlers.

Moral #6: If a constructor has an exception specification, that exception specification must allow for the union of all possible exceptions that could be thrown by base and member subobjects. As Holmes might add, "It really must, you know." (Indeed, this is the way that the implicitly generated constructors are declared; see GotW #69. [9])

Moral #7: Use the Pimpl idiom to hold "optional parts" of a class's internals. If a constructor of a member object can throw but you can get along without said member, hold it by pointer and use the pointer's nullness to remember whether you've got one or not, as usual. Use the Pimpl idiom to group such "optional" members so you only have to allocate once. And finally, one last moral that overlaps with the above but is worth restating in its own right:

Moral #8: Prefer using "resource acquisition is initialization" to manage resources. Really, really, really. It will save you more headaches than you can probably imagine.

The Way It Is

The way the language works is entirely correct and easily defensible, once you think about the meaning of C++'s object lifetime model and philosophy. A constructor exception must be propagated, for there is no other way to signal that the constructor failed. Two cases spring to mind:

// Example 2(c): Auto object
//
{
  X x;
  g( x ); // do something else
}

If X's construction fails — whether it's due to X's own constructor body code, or due to some X base subobject or member object construction failure — control must not continue within the scope of this code block. After all, there is no x object! The only way for control not to continue in f's body is to emit an exception. Therefore a failed construction of an auto object must result in some sort of exception, whether it be the same exception that caused the base or member subobject construction failure or some translated exception emitted from an X constructor function try block. Similarly:

// Example 2(d): Array of objects
//
{
  X ax[10];
  // ...
}

If the fifth X object construction fails — whether it's due to X's own constructor body code failing, or due to some X base or member subobject construction failing — control must not continue within the scope of this code block. After all, if you tried to continue, you'd end up with an array not all of whose objects really exist.

A Final Word: Failure-Proof Constructors?

Is it possible to write and enforce an empty throw-specification for a constructor of a class (like C in Example 1), if some base or member constructor could throw? The answer is no; you could write the "I won't throw anything" empty throw-specification, but it would be a lie because there's no way to enforce it. After all, to enforce a "throws nothing" guarantee for any function, we must be able to absorb any possible exceptions that come our way from lower-level code, to avoid accidentally trying to emit them to our own caller. If you really want to write a constructor that promises not to throw, you can work around possibly throwing member subobjects (e.g., if you can hold them by pointer or Pimpl because they truly are optional) but you can't work around base subobjects — another reason to avoid unnecessary inheritance, which always implies gratuitous coupling.

For a constructor to have an empty throw-specification, all base and member subobjects must be known to never throw (whether they have throw-specifications that say so or not). An empty throw-specification on a constructor declares to the world that construction cannot fail. If for whatever reason it can indeed fail, then the empty throw-specification is inappropriate.

What happens if you wrote an empty throw-specification on a constructor, and a base or member subobject constructor really does throw? The short answer: "Go to terminate. Go directly to terminate. Do not pass try, do not collect $200." The slightly longer answer: the function unexpected gets called, which has two choices — to throw or rethrow an exception allowed by the exception specification (impossible, since it's empty and won't allow anything) or to call terminate. Then terminate, in turn, immediately aborts the program [10]. In automobile terms: screech, crunch.

Summary

A C++ object's lifetime begins only after its constructor completes successfully. Therefore throwing an exception from a constructor always means (and is the only way of reporting) that construction failed. There is no way to recover from failed construction of a base or member subobject, so if construction of any base or member subobject fails, the whole object's construction must fail.

Avoid function try blocks, not because they're evil but just because they offer few or no benefits over plain try blocks — and when you're hiring you'll find that more people understand the latter than understand the former. This follows the principles of picking the simplest solution that's effective, and of writing for clarity first. Constructor function try blocks are useful only to translate exceptions emitted from base or member constructors, and all other function try blocks are rarely if ever useful at all.

Finally, as pointed out repeatedly in Items 8 to 19 of Exceptional C++, use owning objects and "resource acquisition is initialization" to manage resources, and you'll usually avoid having to write try and catch at all in your code, never mind in function try blocks.

Acknowledgments

Thanks to Bobby Schmidt for the questions and discussions that got me thinking about this topic.

Notes and References

[1] For simplicity, I'm speaking only of the lifetime of an object of class type that has a constructor.

[2] The inventor of the Python programming language?

[3] The actual code example being commented upon by the above guru was only slightly different. Referring to the second Parrot object:

{
  Parrot();
  const Parrot& perch = Parrot();

  // ... more code; at this point,
  // only the first temporary object
  // is pushing up daisies ...
}

Get it? It's a lifetime-of-temporaries-bound-to-references joke. Remember it for Bay area parties.

[4] A double pun, can be sung to the chorus of "Satisfaction" or to the opening bars of "Another Brick in the Wall, Part N."

[5] Herb Sutter. Exceptional C++ (Addison-Wesley, 2000).

[6] Convergence is funny sometimes. Long after I started pushing the Pimpl idiom and bashing needless inheritance, I kept on coming across new problems that were solved by using Pimpl or removing needless inheritance, especially to improve exception safety. I guess it shouldn't have been a surprise because it's just this whole coupling thing again: higher coupling means greater exposure to failure in a related component. To this comment, Bobby Schmidt responded in private correspondence: "And maybe that's the core lesson to pull out of this — we've really just rediscovered and amplified the old minimal-coupling-maximum-cohesion axiom."

[7] Not even for logging or other side effects, because there shouldn't be any exceptions from base or member subobject destructors and therefore anything you could catch in a destructor function try block could be caught equally well using a normal try block inside the destructor body.

[8] Bjarne Stroustrup. The C++ Programming Language, Third Edition (Addison-Wesley, 1997), Section 14.4.1.

[9] Available online at http://www.peerdirect.com/resources/gotw069a.html.

[10] You can use functions set_unexpected and set_terminate to have your own handlers invoked, which gives you a chance to do a bit of extra logging or cleanup, but then they'll end up doing much the same things.

Herb Sutter ([email protected]) is chief technology officer of PeerDirect Inc. and the architect of their heterogeneous database replication products. He is secretary of the ISO/ANSI C++ standards committees, a member of the ANSI SQL committee, and author of the book Exceptional C++ (Addison-Wesley).


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.