[comp.lang.c++] Dynamic Type checking and Exception Handling

dsouza@gwen.cad.mcc.com (Desmond Dsouza) (03/19/91)

This came up in the context of arguing that explicit dynamic type
checking is sometimes needed. In a previous posting I described three
cases where such dynamic type checks were "indispensible" :), the third
being exception handling.

Come to think of it, as I understand exception handling:

	The C++ exception handling scheme REQUIRES a limited form
	of dynamic type checking, including identifying derived classes.
	Objects *may not* need access to their types, but type-IDs are
	needed by the run-time system, as are checks of the sort:
	"is class X derived from class Y".

Do I understand this correctly ? See the detailed discussion below.

Reid Ellis <rae@utcs.toronto.edu> writes:

  >   Desmond Dsouza <dsouza@optima.cad.mcc.com> writes:
  >   >Consider the proposed (now accepted) Exception Handling scheme for
  >   >C++. The statement:
  >   >  catch (ErrorClass& o) {
  >   >	....
  >   >  }
  >   >is supposed to 'catch' a deeply nested call to:
  >   >  throw ErrorClass("someError");
  >   >
  >   >Any implementation of this involves a run-time type check, including
  >   >class derivation, on the object being thrown. Can you say 'Meta-class'?
  >
  >   This is simply overloading based on argument type.  Just like having
  >   methods named "foo(Tbase &)" and "foo(Tderived &)".  No "Meta-class"
  >   required.
  >
  >					   Reid

Sorry, but it is *not* simply static overloading. The exception  model is:

	- you CATCH a CLASS of objects (including derived class instances)
	- you THROW an OBJECT ( whose class is determined statically ?? )

The run-time exception handler searches the stack for the first catch
(handler) which can handle the object you are throwing and transfers
control there.

Consider:

--------------------------------------------------
class Error {
    virtual msg() {printf ("Error\n");}
};

class File_Error : public Error {
    virtual msg() {printf ("File_Error\n");}  };
};

main (){

  try {	read_in_file ("non_existent_file"); }	// [1]

  catch (File_Error fe) { ... }			// [2]
}

// in another compilation unit:

class Permission_Error : public File_Error {
    virtual msg() {printf ("Permission_Error\n");}  };
};

read_in_file (char* file) {
    throw ( Permission_Error (file));		// [3]
}	

--------------------------------------------------

Line [2] establishes a handler for the *class* File_Error, and hence
for ALL classes derived from it.

Line [3] raises an exception, by creating and THROWing an *object* of 
class Permission_Error. This should transfer control to line [2]

The combined compile-time and run-time system MUST establish, at line
[3], that the class of the thrown object is derived from File_Error.

  QUESTION: Is it true that that only the static type of the thrown
  object is used in selecting a handler ?

  a. If so, we statically know the type being thrown, and at run-time
     merely need a derived-class check. Line [3] can pass information
     like "Permission_Error:File_Error:Error" to the run-time system,
     line [2] encodes "File_Error", and so we transfer to line [2].
     We do not need run-time access to the dynamic class of an object.

     But what of pointers? Use the statically declared pointer type?
     If so, the example below prints: "Error::Permission_Error", and not
     "File_Error::Permission_Error", since the static return type is
     an Error*, which should not be caught by the File_Error handler.

      main () {
	try {	read_in_file ("non_existent_file"); }

	catch (File_Error* fe) { printf("File_error::"); fe->msg(); }
	catch (Error* e) { printf("Error::"); e->msg(); }
      }

      Error* perm_error ();

      read_in_file (char* file) { throw ( perm_error() ); }

      Error* perm_errror() { return new Permission_Error ; }

  b. If not, it appears we need run-time access to the type of an object:
     The run-time object would be of dynamic type Permission_Error*, which
     would be caught by the File_Error Handler.
     The example above would then print: "File_Error::Permission_Error"

	I suspect [a] is right. The ARM hints at [a], but is not explicit.

	ARM, p 356:
	A 'throw-expression' initializes a temporary of the static
	type of the operand of 'throw' and uses that temporary to
	initialize the appropriately typed variable named in the
	handler. 

In either case, we need a representation of the class Permission_Error
and its base classes in line [3]. The compiler also encodes into line
[2] some representation of the class File_Error.

Lets call this representation, just for grins :), an 'object'. This
'object' needs to encode a class and its base classes, at least by
name, and needs to know something about initializations (e.g. for
variable 'fe' on line [2]).

The class of this 'object' thus has instances which themselves
represent (some aspects of) classes.

Sounds a lot like a "meta-class" to me.


Desmond D'Souza
--

-------------------------------------------------------------------------------
 Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324
 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza