Topic: Modular method combination (was: Virtual functions: should there be more than one kind?)


Author: Rick Busdiecker <rfb@lehman.com>
Date: Sat, 18 Sep 1993 23:15:47 GMT
Raw View
Thanks for the replies.  As I wasn't especially clear in what I was
proposing, I'll try to nail it down a bit.  Also, I'll throw in that,
since making my earlier post, I've implemented a way to do modular
method combination that supports multiple inheritance.  The approach
is a bit bleacherous, but it does allow you to specialize properly
defined member functions based soley on the immediate base classes of
the class that you're defining.

So, what I was proposing was actually two proposals.  I believe that
they are completely independent in that either one could be adopted
without the other.

1.  Add a keyword, say `combined' which could be used when declaring a
    member function.  A member specified as combined would be limited
    in a manner similar to destructors.  It could not accept any
    arguments, nor return any values.  Actually, I think that you
    could relax the restriction on arguments, but let's go slowly here
    at least at first.  The semantics would also be similar to
    destructors (except for the destruction part :-)  When a combined
    member function is invoked, every applicable member function in
    the class hierarchy is invoked in a well defined order.  This
    ordering could be exactly the same as the one defined for
    destructors.  While I'd prefer the ordering used by CLOS's default
    method combination, the only thing that I consider critical is
    that a member function specific to a particular class is invoked
    before any like named member function specific to any of that
    class's base classes.

    Combined methods may be virtual, however they do not necessarily
    have to be.

    Although the syntax is *very* different, this proposal is
    essentially what I implemented when trying to find a modular way
    to do method combination.

2.  Add a syntax for explicitly invoking the next member function in
    a class's method combination sequence.  While, as Jacob suggested,
    something called `call_next_method' probably isn't appropriate.
    However, `inherited' really doesn't quite cut it either.
    Consider, the ARM example with the following class hierarchy:

    W
          / \
         A   B
          \ /
    C

    When invoking f for a C object, the methods are invoked in the
    following order: C, A, B, W.  So the _f invoked immediately
    after A::_f is B::_f although it doesn't really seem appropriate
    to say that A inherits anything from B.

    I was thinking that it might be fitting to define some new syntax
    to attach to the member function name, say <>::f, and let people
    call it whatever they want to :-)

    This mechanism would be restricted only in that it could not be
    used with constructors, destructors or `combined', as defined
    above, member functions.

    Depending on the implementation, this proposed mechanism is either
    less static than the one in my first proposal or else it requires
    an unacceptably massive increase in code size for all but the most
    trivial hierarchies.  However, the mapping of object class and
    method class to next method class can be created during
    compilation.  A relatively straight forward implementation would
    have attempts to invoke the `next method' do a run-time look up
    which could be made fairly similar to way that virtual function
    lookup is done currently.  Of course this does mean that invoking
    a next method when none existed would be a run-time error since
    for a particular `method', the existance of the next method
    depends on the class of the object on which it is being invoked.


What I implemented achieves modularity in the sense that it allows a
classes definition to be independent of it's indirect base classes.
As I've mentioned, the implementation is somewhat bleacherous.  It
requires a parallel class hierarchy for each method.  I tried to come
up with a way to limit it to a single parallel hierarchy parameterized
by the name of the member function, however I couldn't really come up
with a way to specify a like named member function defined on another
class in a generalized way.  This is at least in part due to the fact
that there is no `class object' for C++ classes.  There may be a way
to do this with a special static instance of each class, but if there
is, I haven't figured it out yet.

Anyway, the implementation takes advantage of the fact that
destructors provide modular method combination with a reasonable
ordering.  It still requires that you define two functions, say f and
_f, where _f is the `method' and f is the `generic function'.  f is
defined something like this:

void X::f ()
{
    delete (new Xf (this));
}

The constructor for Xf looks something like this:

Xf::Xf (X* pointer)
{
    set_cache ((void*)pointer);
}

and the destructor looks something like this:

Xf::~Xf ()
{
    ((X*)get_cache ())->_f ();
}

Where get_cache and set_cache are inherited from the root class of the
parallel hierarchy.

I used this approach to define a class hierarchy including 13 classes
and in addition to convincing myself that the approach works, I'm
pretty sure that I turned up a bug in the way that at least one C++
compiler orders destructor methods.  I'll have to investigate a bit
further, but I believe that this is a Cfront derived compiler so the
bug may be fairly widespread, however it doesn't violate the
derived-before-base constraint so I doubt it's particular important.
In fact, I think it reinforces my position that that's the only really
critical property of a default method combination ordering.

The fact that this requires extra classes and the delete (new ...)
silliness is admittedly gross, but the purpose of this was just to see
if there was *some* way to avoid hard coding in explicit knowledge of
the entire class hierarchy and still manage to invoke each applicable
member function exactly once.

Some adjustments would be necessary to deal with things like having a
class which doesn't define a version of _f, but the same basic idea
could be used.  Basically, you just have to avoid having that class's
destructor invoke _f.

So anyway, now I've spelled out what I would like to see in a bit more
detail.  I think that the first proposal, while less than I would like
to see at least solves the basic modularity problem without adding
much at all to the complexity of the language.  If the method
combination ordering of destructors were used, it hardly adds anything
at all.  It simply provides additional access a facility already
provided by the language in an extremely specialized form.  I hope
that my example demonstrates that.

Adding `combined' to the language would eliminate the need for the
bleacherous aspects of my example.  It's biggest drawback, in my
opinion, is that specializations of methods have no control over
whether the `inherited'/`next' methods are invoked the way that they
can with CLOS's call-next-method macro.  While, adding something like
call-next-method feature would require more run-time support than some
people will be happy with, it need not impose any run-time overhead on
programs that to not take advantage of it.
--
Rick Busdiecker <rfb@lehman.com> and <rfb@cmu.edu>
  Lehman Brothers
  3 World Financial Center ``I never did give anybody hell.  I just told
  New York, NY 10285-0900   the truth and they thought it was hell.
  (212) 640-9419       - Harry S. Truman