/*--------------------------------------------------------------------------
Simple example of object-oriented callbacks using the Java-inspired
approach of an "interface", i.e. a pure virtual class with a single
member function.
Compiles, but doesn't run; some functions are omitted.
In C, a function which needs to call its caller repeatedly before returning
often takes a callback function pointer and a context pointer as parameters,
then calls the callback with the context pointer as an argument.
The context pointer is there so the callback function doesn't need to
look at global variables to get at its data.
You can do the same thing in C++; the calling object can pass a pointer
to itself as the context pointer. Writing in C++, you'd like the
function to be a member of some object. It turns out the nicest way to
do this is to ask the caller to inherit from a very small pure
virtual class (an 'interface', in Java terms) and pass you a pointer to it.
Your function uses it as a pointer to the interface, and
just calls the one method it cares about.
This feels very natural to me -- and I'm a hardnosed C programmer.
I think of it as a nice way to express the combination of
"function pointer plus context pointer" in a typesafe way.
The call is a bit more expensive, since it has to do several memory
references to look up the overloaded virtual function, but hey,
this is C++, what's a few CPU cycles between friends.
This technique is, according to the C++ FAQ author, an expression of
the very core of dynamic binding, object-oriented code.
See the archived discussion at bottom of this document.
In Java parlance, this technique is simply called using an Interface.
(Java, having stripped out all but the best bits of C++, can use
simple names for what's left :-)
In "Design Patterns" parlance, this technique is called the
"Observer Pattern". See Amazon's reviews of that book at
http://www.amazon.com/exec/obidos/ASIN/0201633612
See the publisher's pages at http://www.awl.com/patterns/
and the Patterns home page at http://hillside.net/patterns/
Brace yourself - people who like "Design Patterns" have a lot to say
about the subject.
Note that templates can sometimes be used to achieve a
similar effect without the runtime overhead of the virtual function lookup.
Some people prefer one approach, some prefer the other.
See also related discussions in newsgroup comp.lang.c++.moderated
(e.g. http://x40.deja.com/getdoc.xp?AN=548596155)
and the C++ FAQ, http://www.cerfnet.com/~mpcline/c++-faq-lite/
Portions copyright Dan Kegel 27 Nov 1999. Licensed under GPL.
--------------------------------------------------------------------------*/
#include <ostream.h>
#include <unistd.h>
/* Library class to accept data from many sockets */
struct MultiListener {
struct SocketReaderInterface {
/*------------------------------------------------------------------
The operating system has told us that the given socket has data
waiting. The user code must override this pure virtual function
with a real implementation that reads from the socket.
------------------------------------------------------------------*/
virtual void readFromSocket(int socket) = 0;
};
/*------------------------------------------------------------------
Register an object and the socket it wants to listen on.
------------------------------------------------------------------*/
void registerObjectAndSocket(SocketReaderInterface *p, int socket)
{
s_sockets[s_num_sockets].p = p;
s_sockets[s_num_sockets++].socket = socket;
}
/*------------------------------------------------------------------
Check registered sockets for input, call corresponding object's
readFromSocket() method when it's time for that object to read
from its socket.
------------------------------------------------------------------*/
void pollSockets(); // body not shown
// The array set by registerObjectAndSockets, and read by pollSockets
static struct {
SocketReaderInterface *p;
int socket;
} s_sockets[100];
static int s_num_sockets;
};
/* Class to watch any number of time servers, and print out what
* they send us
*/
struct multiTimeListener : public MultiListener::SocketReaderInterface
{
const char *m_hostname;
static MultiListener s_multilistener;
multiTimeListener(const char *hname)
{
int socket = openSocketToTimeServer(hname);
s_multilistener.registerObjectAndSocket(this, socket);
}
// Our implementation of the method specified by SocketReaderInterface
void readFromSocket(int socket)
{
char buf[256];
int nread;
nread = read(socket, buf, sizeof(buf));
buf[nread] = 0;
cout << "Server " << m_hostname << ", time '" << buf << " " << endl;
}
};
/* Program using the above to print out time from many time servers */
main(int argc, char **argv)
{
int i;
multiTimeListener *mtl[100];
for (i=1; i<argc && i<100; i++)
mtl[i] = new multiTimeListener(argv[i]);
for (;;) {
multiTimeListener::s_multilistener.pollSockets();
}
}
/*--------------------------------------------------------------------------
Archived discussion with C++ FAQ author Marshall Cline.
Thanks to Marshall for his patience, and permission to repost his comments.
From: "Dan Kegel" <dank at alumni.caltech.edu>
To: <cline at parashift.com>
Sent: Saturday, November 27, 1999 8:06 PM
Subject: object-oriented callbacks
Hi!
http://www.cerfnet.com/~mpcline/c++-faq-lite/pointers-to-members.html
seems like it ought to mention a few more callback techniques.
One very common in Java is:
when you want an object-oriented callback,
ask the caller to inherit from a very small pure
virtual class (an 'interface', in Java terms)
and pass you a pointer to it.
See http://www.kegel.com/ftpbench/callbackDemo.cc
for an explanation and example.
Another one is functors (which I don't quite get yet,
maybe I'm already doing them).
- Dan
-----------------------------------------------------------------
Date: Sun, 28 Nov 1999 13:31:16 -0600
From: "Marshall Cline" <cline at parashift.com>
To: "Dan Kegel" <dank at alumni.caltech.edu>
Subject: Re: object-oriented callbacks
Callbacks is described in the sections on inheritance. The OO term is
"dynamic binding" but the effect is the same: old code can call new code.
Re functors, let's start describing a "functionoid." A functionoid is an
object that has one major method. It's basically the OO extension of a
C-like function such as printf(). One would use a functionoid whenever the
function has more than one entry point (i.e., more than one "method") and/or
needs to maintain state between calls in a thread-safe manner (the C-style
approach to maintaining state between calls is to add a local "static"
variable to the function, but that is horribly unsafe in a multi-threaded
environment).
A functor is a special case of a functionoid: it is a functionoid whose
method is the "function-call operator," operator()(). Since it overloads
the function-call operator, code can call its major method using the same
syntax they would for a function call. E.g., if "foo" is a functor, to call
the "operator()()" method on the "foo" object one would say "foo()". The
benefit of this is in templates, since then the template can have a template
parameter that will be used as a function, and this parameter can be either
the name of a function or a functor-object. There is a performance
advantage of it being a functor object since the "operator()()" method can
be inlined (whereas if you pass the address of a function it must,
necessarily, be non-inlined).
This is very useful for things like the "comparison" function on sorted
containers. In C, the comparison function is always passed by pointer
(e.g., see the signature to "qsort()"), but in C++ the parameter can come in
either as a pointer to function OR as the name of a functor-object, and the
result is that sorted containers in C++ can be, in some cases, a lot faster
(and never slower) than the equivalent in C.
Since Java has nothing similar to templates, it must use dynamic binding for
all this stuff, and dynamic binding of necessity means a function call.
Normally not a big deal, but in C++ we want to enable extremely high
performance code. That is, C++ has a "pay for it only if you use it"
philosophy, which means the language must never arbitrarily impose any
overhead over what the physical machine is capable of performing (of course
a programmer may, optionally, use techniques such as dynamic binding that
will, in general, impose some overhead in exchange for flexibility or some
other "ility", but it's up to the designer and programmer to decide whether
they want the benefits (and costs) of such constructs).
Marshall
-----------------------------------------------------------------
From: "Dan Kegel" <dank at alumni.caltech.edu>
To: "Marshall Cline" <cline at parashift.com>
Sent: Sunday, November 28, 1999 3:17 PM
Subject: Re: object-oriented callbacks
Thanks for your explanation of functors, it's quite clear.
May I quote you in my page? I already link to your FAQ.
Is there a name for a pure virtual functionoid that's meant to
be an interface specification for a user-defined class,
a la Java interfaces?
Marshall Cline wrote:
> Callbacks is described in the sections on inheritance. The OO term is
> "dynamic binding" but the effect is the same: old code can call new
> code.
The pattern I'm describing is a way to have a library function call
user code repeatedly.
That may simply be old code calling new code, but it's seen from
the point of view of a programmer trying to solve a particular problem,
which might be more accessable than a full description of dynamic binding.
Your FAQ-lite doesn't mention any of this stuff, except in the most
general terms, AFAICS. Perhaps you reserve this for your
printed FAQ?
-----------------------------------------------------------------
Date: Sun, 28 Nov 1999 18:20:26 -0600
From: "Marshall Cline" <cline at parashift.com>
To: "Dan Kegel" <dank at alumni.caltech.edu>
Subject: Re: object-oriented callbacks
Yes you can quote me regarding functionoids and functors (I'll probably add
that stuff to the FAQ at some point, but in the mean time, feel free to
quote me).
But please don't confuse functors (or functionoids) with interfaces/abstract
classes/inheritance/dynamic binding/call backs. Most functors, for example,
have neither base classes nor derived classes - most are stand-alone
concrete classes.
Regarding "having a library function call user code repeatedly," this is a
good thing, and it is the essence of object-orientation and dynamic binding.
It is, to be sure, a somewhat specific application of OO design, but the
underlying approach (of having the user code in a derived class that gets
called by existing code that has base class pointers) is the heart and soul
of all OO designs.
In this sense the FAQ actually spends a great deal of time on this general
pattern. In fact, it is not only the core "meta-pattern" of the C++ FAQ, it
is also the core meta-pattern that is underneath most of the design patterns
in the GOF book. Put it this way: without that meta-pattern, the concepts
of "programming by contract" and "proper inheritance" wouldn't make any
sense.
The point is this: (1) the technique you want to describe is a good
technique; and (2) it is thoroughly discussed in the literature (the GOF
book, the C++ FAQ (both "lite" and "book" variants), most of the other
"design patterns" stuff, and even the Eiffel literature (Bertrand Meyers et
al)).
One more thing: you probably want to check out the section on "proper
inheritance." Since you're using dynamic binding the way it was intended to
be used, you must become an evangelist for "proper inheritance." Work a few
examples and you'll immediately see what I'm talking about. I believe the
C++ FAQ lite gives the ostrich/bird example, and perhaps the circle/ellipse
example. If we accept that a major design goal is to NOT have to change the
"library code" (the pre-existing code that uses base-class-pointers) every
time somebody adds a new derived class, then the value of proper inheritance
becomes clear.
Put it this way: one shouldn't create a derived class to *reuse* the stuff
in the base class; one should create a derived class to *be reused* by the
code that uses base-class-pointers. It's a totally opposite way of looking
at things - it's "right side up" but most people are "upside down."
Marshall
--------------------------------------------------------------------------*/