From: pinky@tamu.edu (The Man Behind The Curtain)
Newsgroups: comp.lang.c++
Subject: Criticisms Wanted:Callbacks&Runtime methods
Date: 12 Dec 1992 02:05:05 -0600
Organization: Texas A&M University
NNTP-Posting-Host: tamsun.tamu.edu
Keywords: callbacks C++ Runtime methods


A few days ago, I posted asking how one could model calling a method
of an arbitary object without knowing whether or not that method
existed for that object.  Basically, I wanted to know if one could
have a runtime determination of an object's methods.

Two responses pointed out the errors in the code I presented -- while
I thank them for this, I understood why it wouldn't work; the code
was an attempt to show what exactly I was trying to do in case my
verbose explanation was insufficient.  A third response via email
was most helpful.  It explained why this wasn't possible in C++,
though one could code a class with this capability.  In addition,
in the December 92 C++ supplement to Dr. Dobb's Journal of Orthodontia,
some articles applied directly to what I was asking for:

p.36s
"By requiring that every member function in a class first be declared
in the class statement, C++ prevents requests from being made of an
object that can't handle them.  Smalltalk and Lisp are less able to
detect errors like these at compile time and provide mechanisms for
dealing with inappropriate object requests during program execution."

But these capabilities are useful, and even necessary when you try
to model the real world.  For instance, might double click a key, and
then select from the screen any object to try to unlock with the key,
be it a bear, bush, or door.  These classes are mostly unrelated, but
in order to be able even attempt the operation, the base class of such
objects must at least have method for unlocking.  If objects can
interact in a wide variety of ways (a large number of verbs), then
the base class would contain an unweildy number of virtual functions.

The second thing that I wanted was for an object to be able to
query the object which calls it, or to act upon the agent verbing it.
(ie. the door could getColor() the key).  The article Using Multiple
Inheritance in C++ was fairly useful in this regard, but it seemed
that the call backs could only be those callbacks anticipated at
compile time, rather than handling such interactions to occur at runtime.

Now, the reply I received suggested that I might impliment these
features using a static table of functions within my objects.
While this might be one way, I believe that the
following impliments both callbacks and runtime methods in
an acceptable way, adding the overhead of an additional switch and
function call to a normal message.  More controversially, I structured
the main program itself to be an object subject to callbacks for
greater consistency in calling methods, with the beneficial side
affect that it becomes easier to model forking concurrent copies of
your program.

What I would like are criticisms, not of the code itself (which is
meant to be illustrative, not documented or structured as it will be
nor with real classes) but of the model which I am using, the way of
representing how objects are interacting.

// callback.cpp - sam inala dec 92
//

#include <iostream.h>
#include <assert.h>

// Macros for declaring verbs available for class

// Example of declaring syntax for class LockedDoor, with verbs Open & Lock
// if include file is missing, it will generate an error.
//
// DECLARE(LockedDoor) verb(Lock); verb(Open); noverb;

#define DECLARE(c) int c::perform(verb act, Object *agent) { switch(act) {
#define verb(v) case Can##v: return v(agent);
#define noverb default: return 0; } }

// Macro: call(obj, v)
// Purpose: to verb an object from within another object
//
// Parameters: obj -> must be a Object * or pointer to a derived class of
//                    Object
//             v   -> A member of the verbs listed in enum verb with the
//                    prefix "Can" removed.  Otherwise, 'undefined symbol'
//                    error generated.
//
// Returns: 0 -> method DNE (Does not Exist)
//         -1 -> method exists; call failed for some reason
//         else  return value of call
//
// Warnings: 1. call(obj,verb) will not work when called from a
//                 function or main, since the "this" pointer DNE.
//
//           2. infinite mutual recursion between two objects is possible

#define call(obj,v) ((obj->perform((Can##v), (this))))

// Verb List

enum verb {	// the token 'verb' is overloaded w/ macro verb(v),
                // but this does not appear to be a problem

    DoNothing, // This verb will never be called.
    CanOpen,
    CanLock,
    CanBurn,
    CanMove
};  // end verb list

class Object {
public:
	virtual int perform(verb, Object *) = 0;
};

// Note: it may be necessary to make perform() take multiple parameters,
// since some verbs require information implicitly (ie. burn at what
// temperature?, move to where?)  This might be avoided by giving the
// calling object methods like getTemperature() which the direct object
// could call back when needing the temperature.

class LockAble : public virtual Object {
public:
	int perform(verb, Object *);
	int Lock(Object *agent) { cout << "Locking..."; return 1; }
};

DECLARE(LockAble) verb(Lock); noverb;

class OpenAble : public virtual Object {
public:
	int perform(verb, Object *);
	int Open(Object *agent) { cout << "Opening..."; return 1; }
};

DECLARE (OpenAble) verb(Open); noverb;

class LockedDoor : public LockAble, public OpenAble {
public:
	int perform(verb, Object *);
	int Open(Object *agent) { cout << "The door creaks open...";

	// if (agent != NULL) // calls should not need to check for NULL ptrs,
	assert(agent != NULL) // since World may receive call backs; and the
                              // call(obj,v) interface should only allows
                              // valid calls.

	call(agent,Move); // example of call-back (Should check return values)
	return 1;
    }
};

DECLARE(LockedDoor) verb(Lock); verb(Open); noverb;

class World : public Object { // No classes should be derived from this.
                              // nor multiple instantiations - no enforcement
                              // of this as of yet.
private:
	LockAble *lobj;
	OpenAble *oobj;
	LockedDoor *ldobj;
	Object *obj;
public:
    World() {
        lobj = new LockAble;
        oobj = new OpenAble;
        ldobj = new LockedDoor;
        obj = (Object *) ldobj;
    }
    int perform(verb, Object *);
    int main(void);           // in a concurrency model, this might be
};                            // the method heartbeat()

inline DECLARE(World) noverb;  // Inline b/c frequent use and simple func.

int World::main(void) {  // Here is the main program -- weirdness indeed!

	call(obj, Open);

	return 0;
}

int main(void) {
        return  // Brave
                (new World) -> main();  // Note: if multiple instantiations
                                        // of World, each with independent
                                        // input device, then a concurrency
                                        // model (simulation) is possible
}

/* Other limitations: no construct similar to

	if method Lock() exists for object x
		/* x -> Lock() */
	else
		say 'Nothing Happens.'

  Because one may only call methods, not simply check their existance,
  except after the fact.  This could be implimented by a macro parallel
  to DECLARE..verb..noverb.  But this seems clumsy.

  Also, how to model an object moving itself from one container
  to another using this framework.
*/

Using this, objects may do things like:
MagicMetalDoor::Unlock() {
	if (BLUE == call(agent, getColor)) // Range of Blue:1..whatever.
	{
		DrawOpeningDoor();
		solidity = PASSABLE;
		..
		return 1; // Unlock called: success
	}
	else return -1; // Unlock called: failure
}

A couple of things might have been useful while developing this, and
I was curious whether or not they might be incorporated in C++ at
any time.  For instance, macros with scope might have been useful,
or macros which could truly handle variable # or parameters.  Something
like #macro mymacro(a,b,...) or any more sophisticated macro processing.
A macro language would be incredibly useful, but that'd be dreaming.

In addition, is there a standardized convention yet for persistent
objects & runtime type identification?  What about transmitting objects
from a defined set of classes over a network?


-- 
Till next time,                \o/   \o/
                                V \o/ V     email:pinky@tamu.edu
<>  Sam  Inala  <>                 V

Newsgroups: comp.lang.c++