roberts@lobot.Tymnet.COM (Michael Roberts) (05/09/90)
Appologies if this has been covered or is particularly brain damaged.
----Synopsis:
C++ seems ill-suited to handling a certain type of message passing.
An example is presented.
A possible extension to C++ is offered.
Hopefully someone can point out an easier way that presently exists!
(Power readers may skip down to "----Extension to C++")
----Overview (real-world motivation):
I have a database of objects (all members of classes derived from a common
base class). Some of these objects are members of a text class. That
text class has methods to respond to editing messages. Also in the
database are version class objects. Version objects each contain a
pointer to a text class object, as well as data and methods for
responding to versioning commands. My application uses version class
objects to provide the interface to the text class objects, and so
protect them from arbitrary editing which does not adhere to versioning
standards.
The natural impulse would be to say that since a version (pun intended ;-)
class object contains more information than a text class object, and since
we want the version class object to inhibit, override or modify the
behavior of the text class object it points to, let the version class be
derived from the text class, with "virtual" methods in the text class.
The problem is that there are many different classes besides the text
class that version class objects can "contain" (point to) and hence
protect. I do not want to have distinct version classes derived from
each of those myriad classes; I want a single version class to handle all
such buffering.
----My present ugly solution:
The version class performs protective services that amount to message
routing and manipulation. I have achieved this via a laborious process of
duplicating each and every message that a contained object can respond to
with a like-named method in the version class. Almost all these version
methods are identical in basic form: they check and update certain version
data, then if conditions are met, send the appropriate editing message to
the contained object. By design, that message always has the same name as
the version method itself.
----Extension to C++: default methods
What I think I want is a mechanism for imitating late binding in C++, that
operates outside the boundaries of inheritence. (I hear the groans
already...) Consider the following scenario. Let the version class be
independent of the other classes. Let it have one or more public methods
called default. This would include default(...), and possibly overloaded
alternatives such as default(int). The compiler would then resolve all
calls to unidentifiable methods in the version class as calls to these
default methods.
The new semantics (and the compilier that reads it) would have to solve
the following problems:
- identification of the actual method called
- simple branching logic based on that identifier
- means for calling a method with that same name for another object
- means for returning the return value from that chained call
To identify which method is actually being called, a special function
would be made available inside the default methods (only): default().
That routine would return a pointer-to-function. The achievement of
simple branching logic would require allowing switch and case statements
to accept such pointers. To pass the original arguments that were in the
incoming call to the outgoing call (if desired) and to return whatever it
is that that outgoing call returns, we might use the following
(incomplete) scheme:
class version
{
private:
data* pObj; // pointer to "contained" object
public:
// various explicit methods...
default default(...);
}
default version::default(...) // returns whatever it should!!!
{
switch (default()) // default() returns int to match
{ // cases of the following style:
case version::whatzit: // equivalently, just "case whatzit:"
//... // perform some desired side effect
return; // whatzit NOT allowed to call through
case version::foo:
// Might read variable length arglist and do something particular
// to this call, perhaps changing, removing or adding an argument.
char c = 'b';
return pObj->default(c);
case version::bar:
// Might even change call entirely:
return someOtherPointer->someOtherMethod(someOtherArg);
default: // its usual meaning: unknown case
return pObj->default(...); // call "passed through", WITH ARGLIST
}
}
main()
{
char c = 'a';
version v;
v.foo(a); // ends up calling some Obj.foo('b');
}
It might be that such constructs are useful for other purposes, such as
pre- and -post routines to place around some methods in a class.
So, the questions are...
- Does C++ already provide other mechanisms to do what I need?
- Has someone else already suggested this?
- Does anybody out there like the basic intent of what I am suggesting?
- Is there a better way, or a more general way to express this?
To those of you who got this far, THANK YOU for your kind attention!
-Mike
----
Mike Roberts (roberts@rocky.tymnet.com) (408) 922-7931
Holding down my end of the bell curve... whichever end I'm on at the moment.sdm@cs.brown.edu (Scott Meyers) (05/09/90)
In article <3550@tymix.UUCP> roberts@lobot.Tymnet.COM (Michael Roberts) writes: : I have a database of objects (all members of classes derived from a common : base class). Some of these objects are members of a text class. That : text class has methods to respond to editing messages. Also in the : database are version class objects. Version objects each contain a : pointer to a text class object, as well as data and methods for : responding to versioning commands. My application uses version class : objects to provide the interface to the text class objects, and so : protect them from arbitrary editing which does not adhere to versioning : standards. [stuff deleted] : The version class performs protective services that amount to message : routing and manipulation. I have achieved this via a laborious process of : duplicating each and every message that a contained object can respond to : with a like-named method in the version class. Almost all these version : methods are identical in basic form: they check and update certain version : data, then if conditions are met, send the appropriate editing message to : the contained object. By design, that message always has the same name as : the version method itself. I think you're going about this backwards. Instead of having class Version police the interface to class Text (and other classes), have class Text (and the other classes) police its own interface, drawing on services provided by class Version. The easiest way to do this is to have Text be a derived class of Version. Then the only functions you have to write for Version are those that truly apply to Version objects -- you don't need to duplicate the functions of the Text objects. Each of the Text functions, in turn, is responsible for calling the appropriate Version function to validate its actions. For example: class VersionObject { protected: Boolean verifyEdit(); ... }; class TextObject: public VersionObject { public: void edit(); ... }; void TextObject::edit() { if (this->verifyEdit()) // do the editing else // issue an error } Class Version shouldn't have too many member functions if, as you imply, most of the checking is pretty standard -- you just write a function for each type of checking required. An additional advantage of this approach is that you can separate out the data in class Text that is primarily used for validation and migrate it up into class Version. Scott
robins@keyboard.esd.sgi.com (Robin Schaufler) (05/10/90)
Mike Roberts proposes a language extension for C++ late binding. I have a similar need for late binding, although I have very different problems to solve. First a description of the problems I'd like to have late binding for, then a different idea for a language extension. The first problem is that if the code for the methods for an object is local to my process, I'd like to just do a function call to a member function, but if it's in another process, I'd like its member functions to perform rpc to the other process. The second problem is that if I have an object in shared memory, I'd like to invoke a virtual member function, and have it look up the address of the function based on both the member function index and the pid. Current ugly solution is to require the code to be in a shared library, and therefore at the same address in both processes, but that's excess mechanism, and shared libraries at SGI don't support breakpoints. About Mike Roberts' problem... I don't think database clients want to know about version objects. It seems more "natural" to call member functions of the text object, and want the version function to be automatically interposed on it. So I'd rather see a runtime interface to virtual function tables. For instance, suppose the text class in Mike's example declares the version class a friend. A version object might save the old member functions as data, and replace them with new member functions that do their thing and then call the old member functions. There would have to be a way to map from the text object to the version object, but that could be done either by sticking a pointer in the base class or with a static table. This doesn't quite solve the problem with the object in shared memory because it doesn't change the algorithm for vtable lookup. But given that Dewhurst and Stark publishes a _new_handler to override error handling behavior of new, it seems to me that there could be an analogous mechanism for overriding vtable lookup. It could even be done as a member function, so long as it isn't virtual. Our issues are as burning for us as this version control issue is for Mike Roberts, so I'd appreciate answers to his questions, too. To reiterate his questions, they are: - Does C++ already provide other mechanisms to do what I (we) need? - Has someone else already suggested this? - Does anybody out there like the basic intent of what I am suggesting? - Is there a better way, or a more general way to express this? To those of you who got this far, THANK YOU for your kind attention! And thank you, Mike, for breaking the ice. Robin Schaufler Standard: the personal flag of the ruler of a state; loosely, a banner. - Webster's New Practical Dictionary
olson@anchor.sgi.com (Dave Olson) (05/10/90)
In article <7484@odin.corp.sgi.com> robins@keyboard.esd.sgi.com (Robin Schaufler) writes: | invoke a virtual member function, and have it look up the address of | the function based on both the member function index and the pid. Current | ugly solution is to require the code to be in a shared library, and therefore | at the same address in both processes, but that's excess mechanism, and | shared libraries at SGI don't support breakpoints. I don't have any comments on the rest of your message, since I'm not really a C++ person, but we DO support breakpoints in shared library routines, at least in 3.3. Dave Olson Life would be so much easier if we could just look at the source code.