TOC Head


We Have Mail


Letters to the editor may be sent via email to cujed@cmp.com, or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.


Dear Mr. Sutter,

In your article “Constructor Failures” (November 2000, CUJ), you state quite unequivocally that there are only two choices for a constructor: either the constructor completes normally and the object exists, or the constructor throws an exception and the object never existed. You note the alternative of setting an internal object status flag on construction failure as “now-obsolete ... outdated, dangerous, tedious, and in no way better than throwing an exception.”

Let’s just see where this logic leads us. When we throw an exception, we are saying that the current path of execution has encountered an error that is so significant that the caller cannot choose to ignore it, and that if there is no code prepared to handle this problem, as indicated by an appropriate catch block, then abrupt program termination is an acceptable consequence. Since you would allow no alternative on constructor failure other than to throw an exception, you are directly implying that if any constructor fails abrupt program termination is an acceptable consequence.

While it is undoubtedly true that this is often the case, I cannot agree that construction failure is always so severe. There are many classes of objects for which construction failure may be reasonably expected to occur. For example, if I pass a filename that does not correspond to an existing file to the constructor of a file stream, that constructor will fail and will set its status so that I can detect that failure. Since this is an error that may be reasonably expected to occur from time to time, especially if the filename is input either directly or indirectly by a human user, then it does not seem to me that throwing an exception is appropriate. Exceptions should be used in exceptional (i.e., not expected) conditions, and not to report errors that should be expected under normal circumstances.

It seems to me that a better guideline would be to throw an exception when the constructor of a critical object necessary for correct program operation fails in a way that is not normally expected, but that construction failure of non-critical objects, or of objects for which construction failure modes are normally expected, should be reported in some less stringent manner. For these non-critical, expected construction failures, the error must still be reported in some manner, but not by an exception. Perhaps setting a status flag is not always such a bad idea after all.

Randy Maddox
maddoxr@acm.org

Herb Sutter responds:

Thanks for the mail. You summarized my point precisely: “there are only two choices for a constructor: either the constructor completes normally and the object exists, or the constructor throws an exception and the object never existed.”

This is exactly true. Indeed, it is directly enforced by the C++ language. A key distinction is between:

a) constructor failure (we couldn’t make a valid T object), which must always be reported by an exception; and

b) other constructor problems that aren’t fatal, where we can still end up making a valid T object that’s safe to use although it might be less than optimal in some way.

Certainly it’s true that “when we throw an exception we are saying that the current path of execution has encountered an error that is so significant that the caller cannot choose to ignore it.” For example:

T t;

// If construction of t fails, the 
// following line is not reliable 
// because t is-not-a T — fortunately,
// the C++ language doesn’t let us get 
// here:
t.MemberFunction();

But I disagree with your conclusion that I’m “directly implying that if any constructor fails abrupt program termination is an acceptable consequence.” That’s not the case at all, because you can’t equate throwing an exception with program termination. The correct equation would be to equate throwing an exception with reporting an error.

I think you’re comparing apples (code that does check for errors) and oranges (code that doesn’t). To illustrate:

// Case 1(a) — APPLE
// Report failure via IsOK() query,
// DO check for error
//
T t;
if( t.IsOK() ) {
  t.MemberFunction();
  ...
}

// Case 2(a) — ORANGE
// Reporting fail via exception,
// DON’T check for error
//
T t;
t.MemberFunction();
...

Incidentally, this is a common mistake when comparing exception-using code with error-code-using code; for example, I’ve seen reports claiming to prove that code written to use exceptions incurs high overhead by saying “this program with error codes is 15% faster than the same code using exceptions” — reports that turn out to be bogus because the code that uses error codes doesn’t check the errors, and it’s invalid to compare code that does no error checking with code that does.

To compare apples with apples, try this:

// Case 1(b):
// Report failure via IsOK() query,
// and DO proper checking
//
T t;
if( t.IsOK() ) {
  t.MemberFunction();
  ...
} else {
  /* cleanup */
}

// Case 2(b):
// Report failure via exception,
// and DO proper checking
//
try {
  T t;
  t.MemberFunction();
  ...
} catch (...) {
  /* cleanup */
}

Or this:

// Case 1(c):
// Report failure via IsOK() query,
// and DON’T do proper checking
//
T t;
t.MemberFunction(); // BOOM!
...

// Case 2(c):
// Report failure via exception,
// and DON’T do proper checking
//
T t;
t.MemberFunction();
...
// BOOM!

I agree that Case 2(c) is inappropriate because the program might blow up if the exception is not handled. But the equivalent Case 1(c) is just as inappropriate because the program will blow up if the error is not checked. Interestingly, note that when you compare the two methods, code that doesn’t check for the errors actually looks the same.

Incidentally, there’s another reason to strongly prefer throwing an exception: using an error code requires the code that tries to create the object to manually handle the error and/or propagate error information back up to someone who can do something about it. Using an exception means the error propagates automatically and can be handled at whatever level is appropriate, which is especially good if the lower-level code doesn’t know enough to recover from the error, as is common if the lower-level code is in a library or template.

Using an exception is simply another method of error reporting in this case, and a decidedly superior one because:

a) failure to construct an object is always serious (and must be checked no matter how the error is reported), because otherwise executing further code that relies on the object being a valid object of its type will probably blow up; and

b) it’s enforced by the Standard (no having to rely on custom behavior).

Having said all that, yes, standard streams do things a little differently. To me, they’re wishy-washy. You write: “if I pass a filename that does not correspond to an existing file to the constructor of a file stream, that constructor will fail and will set its status so that I can detect that failure.” Actually, it may throw ios_base::failure (see 27.8.1.6). Streams seem to be a bit wishy-washy in that instead of deciding on an error-reporting strategy (codes or exceptions), they can be configured in different ways. I think it’s a sign of muddled thinking and/or design by committee.

You add: “Since this is an error that may be reasonably expected to occur from time to time, especially if the filename is input either directly or indirectly by a human user, then it does not seem to me that throwing an exception is appropriate.” Let’s assume for the sake of argument that an exception is not thrown. In this example, I think that’s perfectly fine, because it’s possible to have a perfectly valid file stream object that does not have a valid file at the moment, and it’s intended to be legal; it’s a valid file stream object with a valid state. For example:

ifstream i;
i.open( “foo.bar” );
...
i.close();

Both after construction and after .close(), i is in a valid state. All I’m saying is that this is not an example where, after a file-opening failure in the constructor, we’re left with a “partially constructed” or “failed constructed” ifstream object. We have a valid ifstream object, although personally I would prefer the exception because it’s not the ifstream object I wanted when I asked for that particular constructor (but oh well).

Finally, you hit the philosophical chestnut on the head: “Exceptions should be used in exceptional (i.e., not expected) conditions.” This statement is often repeated, and it’s a rat’s nest because even people who agree wholeheartedly with that statement still disagree vehemently with each other about what “expected” means to them.

In the case of constructor failures, I’m coming at it from the following philosophical point of view: a constructor turns raw memory into a T. If it can’t turn it into a valid T, it must fail by throwing an exception. That view is essentially what the language is designed to enforce. In my mind, a constructor failure is a problem that prevents the constructor from creating a valid T object. In some cases, especially when the object has more than one “mode” or can run with degraded performance, a constructor problem with trying to establish a “higher” mode or “additional” functionality doesn’t mean we can’t create a valid T object.

Let’s say for the sake of argument that construction failure is flagged and checked with IsOK; clearly it’s true that most or all of the time the calling code should check IsOK, else Seriously Bad Things can happen. Wouldn’t you further agree that a language construct that requires the error to be checked is a good thing? Based on Cases 1(c) and 2(c), it seems to me that the “Seriously Bad Things that can happen” are pretty much the same in both models.

Herb

Herb,

Exceptions are a Very Good Thing. Of that there can be no doubt. Code designed and built using exceptions can be much cleaner and easier to follow than code that relies on error codes. The project that I just finished used Broadvision, a web application framework that relies completely on the error code method. Our code that interacted with Broadvision had, as a direct result, about a four-to-one ratio of error-handling code to code that actually did any real work. Exceptions could have made that code a lot cleaner by removing all that error handling from the main line of processing, and by allowing us to centralize the error handling in those contexts that had sufficient information to deal with the errors effectively.

After reading back over your response, I guess my whole argument really does come down to the philosophical question of what an exception should be used for.

My reading about the potential performance overhead introduced by using exception handling, plus too many readings of Stroustrup’s The C++ Programming Language, which is admittedly getting older with each passing day, where he goes to great lengths to stress that exceptions are not intended for normal error reporting, have perhaps had an undue influence on my thinking.

I definitely do have a different opinion now than when I first read your article. I initially disagreed with your point that every constructor failure should result in an exception, but you managed to convince me of the error of my ways. The biggest point I took home from this was the concept of a difference between constructor failure and constructor error. Constructor failure is when we do not construct a valid object that can be used safely, albeit perhaps in a degraded mode of operation. A constructor can experience errors that might lead to a less than optimally constructed object, but as long as that object is safe for use, then it is not necessarily a failure.

Randy Maddox