Introduction

The exception mechanism is an esstential feature of most modern programming lanaugages. Providing a uniform scheme for the reporting and handling of all sorts of "failure" situations, exceptions have replaced many informal practices which were used by programmers whenever a piece of code (usually a function) could fail.
However, a subtle issue arises when considering the combination of exceptions and constructors: It is not obvious how is it possible for a constructor to catch an exception thrown by a constructor of a data members. This document discusses this problem and its solution, in the context of the C++ programming language. Although the problem is relevant to other object-oriented languages, C++ is unique in its value-semantics approach, which makes the solution a little more complicated than in reference-semantics languages (namely: Java, C#).

Problem overview (or: "Where is the catch?")

Let us look at this simple program which defines a String class that represents a fixed-size sequence of characters. A String instance is initialized - at construction time - with a sequence of blank (space) characters. The clients of class String can either observe or change the contents of the sequence by String's subscript operator, operator[].

struct BufError { };

struct Buf {
   char* array_;
   
   Buf(int sz) 
   {
      array_ = (char*) malloc(sz); 
      if(array_ == 0)
         throw BufError();
   }
   
   ~Buf() { free(array_); }
   
   char& at(int offset) { return array_[offset]; }
};

struct StringError { };

struct String {
   Buf buf_;
   int limit_;

   String(int limit) : limit_(limit), buf_(limit) 
   { 
      for(int i = 0; i < limit; ++i)
         (*this)[i] = ' ';
   }
   
   char& operator[](int offset) 
   { 
      if(offset < 0 || offset >= limit_)
         throw StringError();
      else
         return buf_.at(index);
   }   
};

int main(int argc, char* argv) 
{
   try
   {
      String s(200);
      s[0] = 'A';
      cout << s[0] << endl; // Output: 'A'
   }
   catch(BufError& be)
   {
      cerr << "Some String failure" << endl;
   }
   catch(StringError& se)
   {
       cerr << "Some String failure" << endl;
   }
   return 0;
}


Looking at the source code of String we see that it uses a data member of type Buf to maintain the memory area where the characters are actually stored. Note that the compiler makes sure that every non primitive data member is initialized by the constructor. Specifically, class Buf does not have a default constructor, therefore, it is explictly initialized by String's constructor.

In order to examine the subtle problem of exceptions and constructors, let us consider the possiblity of a memory exhaustation. If no memory is available, the constructor of Buf will throw a BufError exception, which will propegate through the call stack up to the catch(BufError& be) clause in main(). However, when insepcting the body of main() we see that there is no difference between a BufError exception and a StringError exception: From main()'s point of view these two exceptions reflect an arbitrary problem with its local variable s.

It is obvious that the code of main() will be more natural and simple if it had to cope with only a single type of exception, namely: StringError, instead of two distinct types. Given that it is not always possible to modify the source code of library classes (specifically, to change the type of thrown exceptions), a general solution requires that class String will convert BufError exceptions into a StringError exception.

The Solution

At first it seems that there is no syntactically legal way to encolse the initializers of data members within a try block. Nonetheless, it turns out that the C++ language does provide the appropriate means for solving this difficulty, using a special kind of a try statement which is formally called "function-try-block". This rarely used feature allows the corresponding catch part to handle ALL exception that were thrown during the execution of the constructor, including those the were emitted by constructors of data members.

The following source code uses a function-try-block to convert a BufError exception into a StringError exception. Pay attention to the try keyword (marked as *1*) which was added to String's constructor.

struct BufError { };

struct Buf {
   char* array_;
   
   Buf(int sz) 
   {
      array_ = (char*) malloc(sz); 
      if(array_ == 0)
         throw BufError();
   }
   
   ~Buf() { free(array_); }
   
   char& operator[](int offset) { return array_[offset]; }
};

struct StringError { };

struct String {
   Buf buf_;
   int limit_;

   String(int limit) 
   try   // <------------------------------------------------- *1* 
      : limit_(limit), buf_(limit) 
   { 
      for(int i = 0; i < limit; ++i)
         (*this)[i] = ' ';          
   }     // <---  function-try-block ends here
   catch(BufError& )
   {
      throw StringError();
   }
   
   char& operator[](int offset) 
   { 
      if(offset < 0 || offset >= limit_)
         throw StringError();
      else
         return buf_[index];
   }   
};

int main(int argc, char* argv) 
{
   try
   {
      String s(200);
      s[0] = 'A';
      cout << s[0] << endl; // Output: 'A'
   }
   catch(StringError& se)
   {
       cerr << "Some String failure" << endl;
   }
   return 0;
}

Additional Comments

Compatibility. Not all compilers support exception-try-blocks. For instance, only the most recent version of Microsoft's Visual C++ compiler support exception-try-blocks. On the other hand, the GCC provides this feature for quite a long time now.

Function-try-blocks are not limited to constructors. Any member function or file-scope function can use them. For instance, the divide() function below, returns 0 if an exception occurs during its execution, ensuring that calls such as divide(5,0) yield a 0 result. Nonetheless, A function-try-blocks is not really needed in member functions since it is equivalent to a "normal" try block that starts before the very first statement within the function and ends immediately after the last statement.

static int divide(int x, int y)
try
{
   return x / y;
}
catch(...)
{
   return 0;
}

Initialization exceptions cannot be hidden. In constructors, a function-try-blocks must throw an exception, or rethrow the same exception it caught. It cannot just absorb an exception and then let the execution continue as if nothing went wrong. Even if you provide and ''do nothing'' catch clause to a function-try-block, the compiler will make sure that the exception is rethrown when the catch clause is exited. Consequently, the two version of class A, below, are equivalent.

// Version 1
struct A
{
   Buf b_;
   
   A(int n) 
   try
      : b_(n)
   {
      cout << "A initialized" << endl;
   }
   catch(BufError& )
   {
      cout << "BufError caught" << endl;
   }
};

// Version 2
struct A
{
   Buf b_;
   
   A(int n) 
   try
      : b_(n)
   {
      cout << "A initialized" << endl;
   }
   catch(BufError& be)
   {
      cout << "BufError caught" << endl;
      throw;      
   }
};