Re: Question: Exceptions in C++

From: Matthew D. Langston (langston@SLAC.stanford.edu)
Date: Mon Sep 27 1999 - 21:41:16 MEST


Hi Marc,

Marc> I have the following problem (not really a problem, but I'd like to
Marc> understand, what's going on).  Compiling the program as follows on AIX
Marc> 4.3 with xlC_r, I get a very different result than on Linux with egcs:

>From a semantic perspective, your results aren't different at all.  As a
matter of fact, your results are semantically identical, at least
according to the C++ standard.  However, your interpretation of the
results being "different" is completely understandable.

What you are seeing is just two different implementations of the C++
standard from two different vendors who have two different
interpretations of how to handle what are commonly known as
"automatically generated temporaries".

To see the source of the confusion, it is necessary to trace through the
path of execution.  I've numbered the lines from your example below in
order to refer to them.

 1.  class Except
 2.  {
 3.     public:
 4.        Except() {   cout << "Except constructor\n"; }
 5.        Except(const Except &e) { cout << "Except copy constructor\n"; }
 6.        ~Except() {   cout << "Except destructor\n"; }
 7.        void fun() { cout << "Except fun\n"; }
 8.  };
 9.
10.  int main()
11.  {
12.     try
13.     {
14.        throw Except();
15.     }
16.
17.     catch( Except x )
18.     {
19.        cout << "Caught exception Except" << endl;
20.        x.fun();
21.     }
22.     catch(...)
23.     {
24.        cout << "Caught undefined exception " << endl;
25.     }
26.
27.     return 0;
28.  }

Sequence of events under Linux:

  1) Construct an unnamed temporary Except object in the scope of the
     "try block" at line 14.  This object is local to the "try block",
     and lives in its stack frame.

  2) From this unnamed temporary, "copy construct" another Except object
     (again at line 14), which is the actual object which is "thrown".

  3) Destroy the first unnamed temporary Except object as we leave the
     "try block" at line 15 (just like all local variables are destroyed
     as their stack frame is exited).

  4) Copy construct the Except object named "x" in the first catch block
     at line 17.  FYI, there is hardly ever a reason to catch an
     exception by value.  Catch exceptions by reference as a matter of
     course.

  5) Invoke the "fun" method on "x" at line 20.

  6) Destroy "x" as we leave the "catch block" at line 21.

  7) Destroy the originally thrown Except object (the one used to
     initialize "x") at line 21.

Note that I am just "guessing" as to the order of events 6) and 7).  The
actual order of when the destructors are run may be reversed.

To gain further insight into the lifetimes of objects (and automatically
generated temporaries in particular) it is instructive to add an "Object
ID number" to your objects that are printed in the constructors and
destructors.  I have done this to your Except class and attached it to
this e-mail.

I have added a column (the third column) to your table below to show
what happens when I changed your example by catching the exception by
reference instead of by value (which, as a general rule, is the proper
way to catch exceptions).  Note that I've used the "instrumented"
version of your Except class so that the sequence of when objects are
constructed and destructed is clearer.

My results for the third column are from using egcs 1.1.2 under Linux
(i.e. the stock compiler under RedHat Linux 6.0 Intel).  I've also
"lined up" the events so that they correspond to one another correctly.

  On AIX, the result is      On Linux I get                        Linux
  (as I would expect it):    (what I don't understand):            (catch by reference)
  -----------------------    ----------------------------------    ----------------------------------
  Except constructor         Except constructor: object #1         Except constructor: object #1
  Except copy constructor    Except copy constructor: object #2    Except copy constructor: object #2
                             Except destructor: object #1          Except destructor: object #1
                             Except copy constructor: object #3
  Caught exception Except    Caught exception Except               Caught exception Except
  Except fun                 Except fun: object #3                 Except fun: object #2
  Except destructor          Except destructor: object #3          Except destructor: object #2
  Except destructor          Except destructor: object #2


--
Matthew D. Langston
SLD, Stanford Linear Accelerator Center
langston@SLAC.Stanford.EDU


#include <iostream>


class Except
{
   public:

      Except()
         : objectID( ++counter )
      {
         cout << "Except constructor: object #" << objectID << endl;
      }

      Except( const Except& e )
         : objectID( ++counter )
      {
         cout << "Except copy constructor: object #" << objectID << endl;
      }

      ~Except()
      {
         cout << "Except destructor: object #" << objectID << endl;
      }

      void fun()
      {
         cout << "Except fun: object #" << objectID << endl;
      }


   private:

      static int counter;
      int        objectID;
};


int Except::counter = 0;


int main()
{
   try
   {
      throw Except();
   }

   catch( Except& x )
   {
      cout << "Caught exception Except" << endl;
      x.fun();
   }
   catch(...)
   {
      cout << "Caught undefined exception " << endl;
   }

   return 0;
}



This archive was generated by hypermail 2b29 : Tue Jan 04 2000 - 00:43:40 MET