Graceful Exits

When you detect an error or invalid state in a function, what's the best way to handle it?


July 01, 2005
URL:http://www.drdobbs.com/graceful-exits/184401979

July, 2005: Graceful Exits

Herb Sutter (http://www.gotw.ca/) chairs the ISO C++ Standards committee and is an architect in Microsoft's Developer Division. His most recent books are Exceptional C++ Style and C++ Coding Standards (Addison-Wesley). Jim Hyslop is a senior software designer for Leitch Technology International. He can be reached at jhyslop@ ieee.org.


"Huh?" My program had just barfed. An access violation somewhere. "Aw, great," I muttered—this was supposed to be a good release build. I fired up the debugger and ran the program again. Sure enough, the debugger kicked in. Only instead of an access violation, I got a debug assertion. "Huh?!?" I examined the source code:

void f( T * t )
{
  assert( t != NULL );
  t->DoSomething();
  [... rest of function ...]
}

I didn't remember that assertion being there before. I did a quick diff against a previous revision:

{
-  if ( t == NULL ) 
               throw invalid_argument;
+  assert( t != NULL );
t->DoSomething();

"Aw, no wonder it crashed. Who's been fracking with this?" I muttered. As if I had to guess. A quick look at the version history confirmed my suspicions: Bob was up to his usual tricks. I restored the test and checked the code back in, after making a quick scan for any other problems Bob may have introduced and running the unit tests.

A while later, I heard something that sounded suspiciously like a latte being sipped behind me. "Hello, Bob," I said without turning around. I smiled at the startled choking sound behind me. I turned around. "What can I do for you?" I asked.

"You can quit mucking around with that code," Bob sputtered as he wiped latte from his shirt.

"What're you talking about?"

"The code you checked in a while ago. I took out that exception thing because it was causing my code to crash. The assertion works fine, and has the bonus that it doesn't cause my code to crash."

A loud snap behind Bob sent a fresh slop of latte onto his shirt. I have to admit I jumped, too—I didn't see the Guru approaching. She must have sneaked up behind Bob's bulk.

"Your code did not crash for the sole reason that you did not run the unit tests before checking in. Changing the exception to an assertion removed the blessed protection of the precondition tests. Without that protection, anything may happen—as is demonstrated by the unit test suite. One of these days, you dispenser of depraved dementia, you are going to check in code that doesn't check its preconditions, and it's going to cost someone dearly!"

"Exceptions, assertions, what's the big deal?" Bob shrugged. "Hey, while I was debugging, I just stepped over those parts where the assertion fired. Once I was past that, the program ran fine." He wandered off, sipping the last of his latte.

The Guru sighed softly.

"Heck," I put in, "even I know why you don't use assert here. It gets pulled out in release builds."

"Very good, apprentice. assert should only be used to trap for programming errors that can occur within a controlled sequence of events. In this...abomination," she waved at the screen, "passing a NULL pointer is a programming error, as the documentation for the function clearly states. However, because f() can be called from any point in the program, it is beyond the power of f() to do anything about its parameters; therefore, an exception is used. In the same vein, assert is also used to test the internal state of a function—such as private member variables, or variables with internal linkage used strictly within the file."

"Uhhh...you mean like in an unnamed namespace?"

"Precisely, my child. Consider this parable," the Guru started writing on the whiteboard:

// file: T.h 
// (assume include guards and all
// necessary #includes present)
class T
{
  void PrivateAccess( void * v )
  {
    assert( v != NULL );
  }
public:
  void PublicAccess( void * v )
  {
    if ( v == NULL ) 
      throw std::invalid_argument;
      // etc....
  }
};

// somefile.cpp
namespace {
  void LimitedAccess( T * t )
  {
    assert( t != NULL );
    // ... do something with t...
  }
}

"Because T::PublicAccess has no control over its environment, it does not use an assertion, but instead throws an exception to preach to its callers about the errors of their ways. On the other hand, the PrivateAccess function calls assert because there is a very limited number of ways the function can be accessed. Similarly, LimitedAccess can be accessed only by functions that exist in the same translation unit, so it can use a simple assertion. Both these methods assume that there is a sufficient and complete test suite available to verify the blessedness of the scribe's writings."

"But why throw an exception?" I interjected. "Why not just return an error code? If f() used a return code, that'd keep Bob happy, I think. Besides, aren't exceptions for exceptional circumstances?"

"No, apprentice, exceptions are not just for exceptional circumstances. Furthermore, how does one define 'exceptional'?

"In these writings, the error is quite severe. Return codes can be ignored, exceptions cannot. As Bob found out. Exceptions also have the added feature of allowing the error to propagate up the stack. This can be used to allow a quick exit to deeply nested, recursively called functions. Some of the search algorithms in the blessed Boost Graph Library use this technique."

"So if it wasn't such a severe error, I could just bail out with an error code?"

"Yes, my child. Some disciples espouse using exceptions to check parameters and other preconditions, and error codes to report processing errors within the function. There is a nice sort of symmetry to that—if I, as the disciple invoking the function, catch an exception, then I know my function has done something wrong: I must pay attention to the requirements of the function I'm calling. Otherwise, an error code indicates that there is infamy outside my code. This is, of course, merely one approach disciples may take to the question of return codes versus exceptions."

"One drawback to exceptions, of course, is that they can be more difficult to debug. By the time you catch an exception, you have no record of where the exception was thrown."

"Y'know," the Guru said, dropping her schtick, "it's getting late. Grab your coat and let's get out of here. I'm meeting Wendy and Kerry at the pub down the street—want to join us?"

"Sure thing." I logged out, shut down, and headed out the door.

Note from the Authors

As is our custom, we have added several layers of meaning to this article. The title not only refers to the topic itself, but also refers to the fact that we have decided it's time for The Guru, the narrator, Wendy, Kerry and, of course, the infamous Bob to make their final exits. It's hard to believe, but we've been writing this column for five years now. A lot has changed in the C++ world in those five years—the introduction of template metaprogramming, CLI emerging as an International Standard, and a whole lot more.

Even though the time has come to retire these characters, you will note that we have not done anything drastic—none of the major characters has been fired, killed, or otherwise changed significantly. Who knows, we may want to revisit this format in the future. By the way, those of you who enjoy "Sutter's Mill" will be glad to know it will continue.

We would like to thank you, our readers, for your support and encouragement over the years. We have tried to respond personally to all e-mails sent to us—if you sent us an e-mail and we never acknowledged it, please forgive the oversight, but rest assured that we did at the very least read it. A lot of you wrote to say you work with, or knew of, a "Bob." And to think—in the first draft of the story that introduced Bob, we fired him right away.

We would also like to thank the CUJ editorial and production staff, both current and their predecessors, for their patience and grace even when we were well past publication deadlines when we sent in the manuscripts (like this one).

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.