MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Cleaning up

Part of the magic of exception handling is that you can pop from normal program flow into the appropriate exception handler. This wouldn’t be very useful, however, if things weren’t cleaned up properly as the exception was thrown. C++ exception handling guarantees that as you leave a scope, all objects in that scope whose constructors have been completed will have destructors called.

Here’s an example that demonstrates that constructors that aren’t completed don’t have the associated destructors called. It also shows what happens when an exception is thrown in the middle of the creation of an array of objects, and an unexpected( ) function that rethrows the unexpected exception:

//: C23:Cleanup.cpp
// Exceptions clean up objects
#include <fstream>
#include <exception>
#include <cstring>
using namespace std;
ofstream out("cleanup.out");

class Noisy {
  static int i;
  int objnum;
  enum { sz = 40 };
  char name[sz];
public:
  Noisy(const char* nm="array elem") throw(int){
    objnum = i++;
    memset(name, 0, sz);
    strncpy(name, nm, sz - 1);
    out << "constructing Noisy " << objnum
      << " name [" << name << "]" << endl;
    if(objnum == 5) throw int(5);
    // Not in exception specification:
    if(*nm == 'z') throw char('z');
  }
  ~Noisy() {
    out << "destructing Noisy " << objnum
      << " name [" << name << "]" << endl;
  }
  void* operator new[](size_t sz) {
    out << "Noisy::new[]" << endl;
    return ::new char[sz];
  }
  void operator delete[](void* p) {
    out << "Noisy::delete[]" << endl;
    ::delete []p;
  }
};

int Noisy::i = 0;

void unexpected_rethrow() {
  out << "inside unexpected_rethrow()" << endl;
  throw; // Rethrow same exception
}

int main() {
  set_unexpected(unexpected_rethrow);
  try {
    Noisy n1("before array");
    // Throws exception:
    Noisy* array = new Noisy[7];
    Noisy n2("after array");
  } catch(int i) {
    out << "caught " << i << endl;
  }
  out << "testing unexpected:" << endl;
  try {
    Noisy n3("before unexpected");
    Noisy n4("z");
    Noisy n5("after unexpected");
  } catch(char c) {
    out << "caught " << c << endl;
  }
} ///:~ 

The class Noisy keeps track of objects so you can trace program progress. It keeps a count of the number of objects created with a static data member i, and the number of the particular object with objnum, and a character buffer called name to hold an identifier. This buffer is first set to zeroes. Then the constructor argument is copied in. (Note that a default argument string is used to indicate array elements, so this constructor also acts as a default constructor.) Because the Standard C library function strncpy( )stops copying after a null terminator or the number of characters specified by its third argument, the number of characters copied in is one minus the size of the buffer, so the last character is always zero, and a print statement will never run off the end of the buffer.

There are two cases where a throw can occur in the constructor. The first case happens if this is the fifth object created (not a real exception condition, but demonstrates an exception thrown during array construction). The type thrown is int, which is the type promised in the exception specification. The second case, also contrived, happens if the first character of the argument string is ‘z’, in which case a char is thrown. Because char is not listed in the exception specification, this will cause a call to unexpected( ).

The array versions of new and delete are overloaded for the class, so you can see when they’re called.

The function unexpected_rethrow( ) prints a message and rethrows the same exception. It is installed as the unexpected( ) function in the first line of main( ). Then some objects of type Noisy are created in a try block, but the array causes an exception to be thrown, so the object n2 is never created. You can see the results in the output of the program:

constructing Noisy 0 name [before array]
Noisy::new[]
constructing Noisy 1 name [array elem]
constructing Noisy 2 name [array elem]
constructing Noisy 3 name [array elem]
constructing Noisy 4 name [array elem]
constructing Noisy 5 name [array elem]
destructing Noisy 4 name [array elem]
destructing Noisy 3 name [array elem]
destructing Noisy 2 name [array elem]
destructing Noisy 1 name [array elem]
Noisy::delete[]
destructing Noisy 0 name [before array]
caught 5
testing unexpected:
constructing Noisy 6 name [before unexpected]
constructing Noisy 7 name [z]
inside unexpected_rethrow()
destructing Noisy 6 name [before unexpected]
caught z 

Four array elements are successfully created, but in the middle of the constructor for the fifth one, an exception is thrown. Because the fifth constructor never completes, only the destructors for objects 1–4 are called.

The storage for the array is allocated separately with a single call to the global new. Notice that even though delete is never explicitly called anywhere in the program, the exception-handling system knows it must call delete to properly release the storage. This behavior happens only with “normal” versions of operator new . If you use the placement syntax described in Chapter 11, the exception-handling mechanism will not call delete for that object because then it might release memory that was not allocated on the heap.

Finally, object n1 is destroyed, but not object n2 because it was never created.

In the section testing unexpected_rethrow( ), the n3 object is created, and the constructor of n4 is begun. But before it can complete, an exception is thrown. This exception is of type char, which violates the exception specification, so the unexpected( ) function is called (which is unexpected_rethrow( ), in this case). This rethrows the same exception, which is expected this time, because unexpected_rethrow( ) can throw any type of exception. The search begins right after the constructor for n4, and the char exception handler catches it (after destroying n3, the only successfully created object). Thus, the effect of unexpected_rethrow( ) is to take any unexpected exception and make it expected; used this way it provides a filter to allow you to track the appearance of unexpected exceptions and pass them through.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru