The function
facility, recently adopted by the C++ standards committee,
provides a generalized way of working with arbitrary functions when all you
know (or need to know) is their approximate signature. It turns out that this
facility enables us to write, among other things, generalized implementations
of important design patterns like Observer. Those generalized design patterns,
in turn, motivate two small but important extensions to function
itself.
[Note: In both the two previous columns, I promised to talk about inline
in "the next column." Last time I put off inline
in order to cover another
fun topic, namely private
. This time I've again decided to put off inline
because I had so much to say about function
in my current The New
C++ column [1] that it turned into two columns - one to describe function
itself (which was the topic of [1]), and one to describe a generalized Observer
implementation that uses function
and motivates changes and extensions
to function
(this article). I'm going to stop making promises about inline
,
but I really do intend to treat it once I stop thinking of even more interesting
things to write about.]
In [1], I gave an overview of one of the first two extensions that were approved
in October 2002 for the standard library extensions technical report (the "Library
TR"): Doug Gregor's proposal for polymorphic function object wrappers. [2] This
function
facility comes directly from the Boost project [3], an important
(and free) collection of C++ libraries available for you to download and try
out today.
In brief, the function
facility provides a generalized way of working
with arbitrary functions when all you know (or need to know) is their signature.
In fact, as it turns out, you don't even need to know the target function's
exact signature - any target function with a compatible signature, meaning one
where the parameter and return types are appropriately convertible, will work
just fine. A function
can bind to a nonmember function, a member function,
or a functor that defines its own operator()
and can therefore be called
just like a function. (I'm going to use the term "functors" for the latter,
instead of the alternative "function objects," to avoid confusion with "function
objects" which means objects of the function
library facility we're discussing.)
In this article, I'll focus on showing how function
lets us simplify
and expand the classic Observer pattern. Along the way, I hope to persuade you
that Observer (and other patterns like it) compellingly demonstrates not only
the usefulness of function
, but also why function itself should
be further generalized beyond its current state in the draft standard library
technical report.
A Motivating Example for function: The Observer Pattern (or, Beyond Singlecast)
Briefly, the Observer pattern "define[s] a one-to-many dependency between objects so that when one object [the subject] changes state, all its dependents [its observers] are notified and updated automatically." [4] Observer commonly arises in event-driven systems, such as operating systems and GUI environments (e.g., Smalltalk's Model/View/Controller framework), where objects or processes need to respond to state changes elsewhere in the system (e.g., external input) without knowing in advance when they might happen. Allowing such observers to idle and only respond to events as they occur is almost always better than requiring them to perform constant polling, or busy-waiting, just in case an event might have happened since the last time the observers looked in on their subject.
As described in [4] in an object-oriented form, a Subject lets observers register themselves for later callback. The observers inherit from a common base class, and the subject keeps a list of base class pointers. Figure 1 shows the structure of this object-oriented form of Observer:
Figure 1: Observer pattern's structure, as it appeared in Design Patterns [4]
The code might look something like the following Example 2 (adapting the example
in [4]). I've kept Subject's
member functions as virtual for consistency
with [4], even though it's debatable whether or not that is a generally useful
choice.
// Example 2: A sample OO Observer implementation // class Subject; class Observer { // ... public: virtual void OnUpdateOf( Subject* ) = 0; }; class Subject { // ... public: virtual void Attach( Observer* o ) { obs_.insert( o ); } virtual void Detach( Observer* o ) { obs_.erase( o ); } virtual void Notify() { for( set<Observer*>::iterator i = obs_.begin(); i != obs_.end(); ++i ) (*i)->OnUpdateOf( this ); } private: set<Observer*> obs_; };
Here's an example use, following [4]:
class ClockTimer : public Subject { // ... void Tick() { // ... timekeeping logic... Notify(); // every so often, tick } }; class DigitalClock : /*...*/ public Observer { // ... public: DigitalClock( ClockTimer* t ) : timer_(t) { timer_->Attach( this ); } ~DigitalClock() { timer_->Detach( this ); } void OnUpdateOf( Subject* timer ) { /* query timer and redraw */ } private: ClockTimer* timer_; };
Note that in this OO version of the pattern, Subject
has two limitations:
First, it is hardwired to be observable only by an object of a type that inherits
from Observer
. Second, the callback function itself has to have a prescribed
name and exact signature. Both of these limitations arise because Subject
stores its observers list using object pointers into a fixed hierarchy.
How can we remove these limitations? As a first step, we might think we could
alleviate the second limitation somewhat by storing a member function pointer,
which would theoretically allow the callback function to have any name, although
it would still have to have a fixed signature. There are several problems with
this approach: First, for the callback functions to be useful they must still
be predeclared as pure virtuals in the Observer
base class, which means
that although the callback gains some name flexibility, it still has to be one
of a predefined set of special known names (reminiscent of Henry Ford's famous
quote, "you can have any color you want, as long as it's black"). Second, the
Subject
would have to store the member function pointer not instead of,
but in addition to, the object pointer, so that it knows what object
to dispatch to.
Fortunately, there is a better way.