[comp.lang.c++] Getting Type Information at Runtime

sdm@cs.brown.edu (Scott Meyers) (05/25/90)

I'm currently investigating ways to get the type of an object at runtime.
What I want to come up with is a simple way to do things like

    switch (object->type()) {
      case class1:  dosomething1(); break;
      case class2:  dosomething2(); break;
      case class3:  dosomething3(); break;
    }

This rules out simply returning a string containing the classname.

I'd also like the scheme to be fairly foolproof and robust, meaning that
multiple people working semi-independently on the same system can easily
create classes that support the protocol without stepping on each other's
toes, and adding new classes doesn't normally require modifying anything
outside that class.  This rules out using an enumerated type.  A standard
scheme seems to be to declare a static char in each class, then use the
address of that char as the identifier for the class.  This approach seems
promising, but in trying to put all the pieces together, I've found it
isn't always as smooth as I'd hoped.  For example, cfront complains if the
static char isn't initialized, so you have to put superfluous
initializations in your code...

Finally, I'd like the approach to be extendable so that I can test "isa"
relationships.  That is, I can say things like

    if (isa(object1, object2)) ...

and the condition will evaluate to true iff object1 and object2 are of the
same class or there is a public inheritance path from object2 to object1.
It should also be possible to test this relationship using class
identifiers, e.g., something like

    if (isa(object1, placeHolderForClassX)) ...
    if (isa(placeHolderForClassX, object1)) ...

I know that various libraries have implemented schemes that offer this kind
of functionality.  What I'd like to know is how they do it, and what the
advantages and disadvantages are.  Is there a simple, foolproof, robust,
way to get and test class relationships at runtime?

Scott Meyers
sdm@cs.brown.edu

johnson@p.cs.uiuc.edu (05/26/90)

Scott Meyers writes:
>I'm currently investigating ways to get the type of an object at runtime.
>What I want to come up with is a simple way to do things like
>
>    switch (object->type()) {
>      case class1:  dosomething1(); break;
>      case class2:  dosomething2(); break;
>      case class3:  dosomething3(); break;
>    }

I assume that you have a better reason for wanting the type of an object
at runtime, since the purpose of virtual functions is to eliminate code
like this.

You are requiring that the type of an object be represented by an integer,
that this integer can be assigned without any centralized coordination,
and that it is possible to compare integers representing types to test
whether one is a subtype of the other.  One way to solve this problem
is to create an object for each type.  Define a type ClassObject with
member variables "name" and "supertypes".  Every class that you define
can have a static member variable of type ClassObject that is initialized
to know the name and supertypes of the class that it represents, as well
as a "type()" function that returns its address.

We do something like this for Choices to implement our debugger and
browser.  We do NOT use it in switch statements.

This has the disadvantage that every class has to be a subclass of
"TypeableObject".

Ralph Johnson -- University of Illinois at Urbana-Champaign

wevg0324@uxa.cso.uiuc.edu (05/26/90)

	First let me say that I DO recognize that sometimes it is very
useful to determine an object's class at runtime, expecially in debug printf's.
HOWEVER:
> What I want to come up with is a simple way to do things like
>    switch (object->type()) {
>      case class1:  dosomething1(); break;
>      case class2:  dosomething2(); break;
>      case class3:  dosomething3(); break;
>    }
	You almost certainly do NOT want to use a switch statement here.
Instead use virtual methods (they are both faster, and "more object oriented").
Replace that switch statement with:
	virtual class1::switch_dosomething() { dosomething1(); };
	virtual class2::switch_dosomething() { dosomething2(); };
	virtual class3::switch_dosomething() { dosomething3(); };
and the call:
	object->switch_dosomething();
===============================================================================
> Finally, I'd like the approach to be extendable so that I can test "isa"
> relationships.  That is, I can say things like
>    if (isa(object1, object2)) ...
> and the condition will evaluate to true iff object1 and object2 are of the
> same class or there is a public inheritance path from object2 to object1.

This is a seperate problem.  First create a root super class for all
the objects you wish to compare with isa relations.  Then for each
subclass $name, add the following method to the "root" class, expanding
"$name" as appropriate:
	virtual kindOf$name() { return(false); };
Then in each class "$ThisClass" including "root" add the methods:
	virtual kindOf$ThisClass() { return(true); };
	virtual isa(root *object2) { return(object2->kindOf$ThisClass()); };

NOTE: The call object->kindOf$name() answers the question,
NOTE: is it legal to assign the "object" pointer to a
NOTE: variable of type "$name *ptr" without a cast.

Now rewrite your "if (isa(object1, object2)) ..."
statement as     "if (object1->isa(object2)) ..."

> It should also be possible to test this relationship using class
> identifiers, e.g., something like
>     if (isa(object1, placeHolderForClassX)) ...
>     if (isa(placeHolderForClassX, object1)) ...

Just use the "kindOf$name()" methods directly.
===============================================================================
A closely related trick you may find useful is the method "asA$name()."
The purpose of this method is to allow you to do the following in a
typesafe manner (no casts):
	root_class *root;
	child_class *child1, *child2;
	...
	root = child1;   // Always legal in C++
	...
	child2 = root;   // Normally a cast is needed on this line
			 // but a cast tells the compiler "trust me."

In the root class, for each subclass $name add the method:
	virtual *$name asA$name() { return(0); };  // Or perhaps assert(0).
Then in each class add the method:
	virtual *$name asA$name() { return(this); };
===============================================================================
billvoss@uiuc.edu  or  voss@cs.uiuc.edu  or voss@a.cs.uiuc.edu

Bill Voss  --  Graduate Student, Department of Computer Science
	       University of Illinois in Urbana/Champaign