Suggested Extension #2: Multicast Support
The ability to notify multiple observers is key to the Observer pattern. This demonstrates a common use for functions, namely to implement multicast - the ability to remember, and later fire, multiple functions.
The good news is that, to get multicast support, we don't need to change function
,
we only need to build on it. By direct analogy to the Attach
and Detach
code in Example 3, we can build a multi_function
that supports multicast
to an abitrary number of compatible function
s, with the caveat that as
long as function doesn't provide equality comparison we still can't ignore duplicates
when adding function
s to a multicast set, and we can't remove function
s
individually from a multicast set. In a moment I'll show a sample implementation
of multi_function
. But first, let's see what we want the code using it
to look like. Example 4(a) shows how using such a multi_function
would
look in the ideal case (that is, if function supported equality comparison):
// Example 4(a): Using an ideal multi_function, // if function supports equality comparison // int f( int ); int g( int ); char h( long ); multi_function<int(int)> m; m += f; // ok, adds f (synonym for "m.add( f );") m += g; // ok, adds g (synonym for "m.add( g );") m += h; // ok, adds h (synonym for "m.add( h );") m += g; // ok, but ignores the duplicate m -= f; // ok, removes f (synonym for "m.remove( f );") m( 42 ); // calls g and h once each (in some order)
Example 4(b) demonstrates using a multi_function
built on only function
as it exists today:
// Example 4(b): Using a hobbled multi_function, // if function does not support equality comparison // int f( int ); int g( int ); char h( long ); multi_function<int(int)> m; m += f; // ok, adds f m += g; // ok, adds g m += h; // ok, adds h m += g; // ok, adds g again because it can't ignore duplicates m -= f; // no-op (or error), impossible to implement with the desired semantics m( 42 ); // calls f once, g twice, and h once (in some order)
Now we can somewhat simplify the generalized Observer from Example 3:
// Example 5: A generalized Observer pattern // structure, using multi_function // class Subject { // ... public: virtual void Attach( function<void (Subject*)> o ) { obs_ += o; } virtual void Detach( function<void (Subject*)> o ) { obs_ -= o; } virtual void Notify() { obs_( this ); } private: multi_function<void (Subject*)> obs_; };
If your reaction to first Example 3 and then Example 5 was something like, "boy, that sure simplifies Observer," you're right. In fact, you can now fully library-ize the Observer pattern:
// Example 6: A fully generic pattern implementation // template<typename F> class Subject { public: virtual ~Subject() { } // see [5] virtual void Attach( function<F> o ) { obs_ += o; } virtual void Detach( function<F> o ) { obs_ -= o; } protected: multi_function<F> obs_; };
I chose to make the multi_function
object protected so that derived
classes can access it directly. Protected data is usually a bad thing, but in
this case it didn't seem worthwhile to wrap it with a series of templatized
Notify
functions (one for each number of parameters) which didn't do
anything.
Well, that was almost too easy: A complete and generalized implementation of the Observer pattern in a handful of lines of code. Well, maybe this doesn't quite cover all possible subjects, because not all subjects will care about overriding these functions — so while we're at it, let's also provide a nonvirtual version:
template<typename F> class NonvirtualSubject { public: void Attach( function<F> o ) { obs_ += o; } void Detach( function<F> o ) { obs_ -= o; } protected: ~NonvirtualSubject() { } // see [5] multi_function<F> obs_; };
Any class can now make itself observable by simply inheriting from the appropriate
instantiation(s) of Subject<>
. Whereas all of our examples so far followed
the pattern in [4] where the callback to the observing object included a Subject*
argument, that's not always needed and now we can choose to make the observer
callback signature anything we want:
class ClockTimer : public NonvirtualSubject<void()> { // ... void Tick() { // ... timekeeping logic... obs_(); // every so often, tick } };
And, if we want to be observable by different types of observers, we can combine them to boot:
class OtherTimer : public NonvirtualSubject<void()> , public NonvirtualSubject<int(string)> { // ... void Tick() { // ... timekeeping logic... NonvirtualSubject<void()>::obs_(); // every so often, tick } void Tock() { // ... whatever logic... NonvirtualSubject<int(string)>::obs_( "tock" ); // every so often, tock } };
Cool, slick, and definitely gnarly. But all of this hinges on multi_function
,
so it's time to consider the final question: What would multi_function
's
implementation look like?
Implementing multi_function
When implementing multi_function
, we have a couple of design choices to
make:
- What should
operator()
return? We could be invoking an arbitrary number of targets, so should we return all of the values? none of them? one or some of them? some calculated value (e.g., for the special case ofbool
return values, or'ing them all together)? In my view the only viable choices in the general case are "all" or "none." Implementing the "all" choice just requires the equivalent of constructing acontainer<ReturnType>
containing the return values. That's easy enough to code, but it's usually not preferable and I'll demonstrate the "none" choice below. - Should there be a constructor option to select whether
multi_function
ignores duplicates or not? Perhaps. I'll just demonstrate an implementation that always ignores duplicates.
Given those choices, here is a sample implementation of multi_function
that is not intended to be complete, but to show how it can be done. I've implemented
it in terms of the Boost implementation of function
, but after the first
few lines it's compatible with the draft standard specification of function
,
including the standard one is in namespace std::tr1
(for now):
// Example 7: multi_function sample implementation // #include <list> #pragma warning(disable:4224) #pragma warning(disable:4100) #include <boost/function.hpp> namespace tr1 = boost; template<typename F> class multi_function : public tr1::function<F> { public: typedef std::list<typename tr1::function<F>::result_type> result_type; typedef typename tr1::function<F>::allocator_type allocator_type; explicit multi_function() { } // the implicitly generated copy constructor, // copy assignment, and destructor are fine template<typename F2> multi_function( tr1::function<F2> f ) { l_.push_back( f ); } // if you have an implementation of function that supports // equality comparison, uncomment the calls to remove void add ( tr1::function<F> f ) { /* l_.remove( f ); */ l_.push_back( f ); } void remove( tr1::function<F> f ) { /* l_.remove( f ); */ } void operator+=( tr1::function<F> f ) { add( f ); } void operator-=( tr1::function<F> f ) { remove( f ); } void swap( multi_function& other ) { l_.swap( other.l_ ); } void clear() { l_.clear(); } bool empty() const { return l_.empty(); } operator bool() const { return l_.empty(); } void operator()() const { for( std::list<tr1::function<F> >::const_iterator i = l_.begin(); i != l_.end(); ++i ) (*i)(); } template<typename T1> void operator()( T1 t1 ) const { for( std::list<tr1::function<F> >::const_iterator i = l_.begin(); i != l_.end(); ++i ) (*i)( t1 ); } template<typename T1, typename T2> void operator()( T1 t1, T2 t2 ) const { for( std::list<tr1::function<F> >::const_iterator i = l_.begin(); i != l_.end(); ++i ) (*i)( t1, t2 ); } template<typename T1, typename T2, typename T3> void operator()( T1 t1, T2 t2, T3 t3 ) const { for( std::list<tr1::function<F> >::const_iterator i = l_.begin(); i != l_.end(); ++i ) (*i)( t1, t2, t3 ); } // etc. private: std::list<tr1::function<F> > l_; }; template<typename MF1, typename MF2> void swap( multi_function<MF1>& mf1, multi_function<MF2>& mf2 ) { mf1.swap( mf2 ); } template<typename MF1, typename MF2> bool operator==( const multi_function<MF1>& mf1, const multi_function<MF2>& mf2 ) { // if function doesn't provide comparisons, this is unimplementable // if function provides op==, this is inefficient: O(N^2) operation // if function provides op<, this is efficient: can keep lists sorted, or use a set // for now, install a placeholder: return false; } template<typename MF1, typename MF2> bool operator!=( const multi_function<MF1>& mf1, const multi_function<MF2>& mf2 ) { return !( mf1 == mf2 ); }
I took some shortcuts here, but the code is a working example that you can try out today.
Conclusion: Beyond Observer
Observer is only one of several patterns in [4] whose implementation
can be generalized using function
; two other examples are Strategy and
State (for a description of state machines' implementation in terms of plain
function pointers, see also More Exceptional C++ Item 35 [6]). In such
patterns, replacing calls to a base class virtual function (whose overrides
must always have a fixed signature and can only exist in a predefined class
subhierarchy) while a call to a function
of the same signature still
allows all the calls that were allowed before, as well as calls to functions
and functors outside the original inheritance hierarchy, and calls to functions
with different but compatible signatures. Thus changing the patterns' structure
from an object-oriented structure based on virtual function dispatch to a more
generic one that does not depend on inheritance hierarchies lets us broaden
and extend the usefulness and flexibility of those patterns.
Postscript
After writing this article, I discovered Phil Bass's recent Overload articles
[7] on implementing Observer in C++, which independently discovered some of the
same things we've covered in this article. The approach [7] does not mention function
,
and focuses mostly on developing a function
-like Event
class. It
demonstrates a generalized Observer
that does not require a monolithic
hierarchy rooted at an Observer
base class, but the solution still has
the drawbacks of being unable to ignore duplicates or detach specific observers
by value (it uses the cookie/iterator workaround for the latter).
Acknowledgments
Thanks to Doug Gregor and Peter Dimov for their comments on drafts of this material.
References
[1] H. Sutter. "Generalized Function Pointers" (C/C++ Users Journal, 21(8), August 2003).
[2] D. Gregor. "A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library," ISO/ANSI C++ standards committee paper (ISO/IEC JTC1/SC22/WG21 paper N1402, ANSI/NCITS J16 paper 02-0060).
[3] www.boost.org
[4] E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns (Addison-Wesley, 1994).
[5] H. Sutter. "Virtuality" (C/C++ Users Journal, 19(9), September 2001). Available online at http://www.gotw.ca/publications/mill18.htm.
[6] H. Sutter. More Exceptional C++ (Addison-Wesley, 2002).
[7] P. Bass. "Implementing the Observer Pattern in C++," Parts 1 and 2 (Overload 52 and 53, December 2002 and February 2003).
About the Author
Herb Sutter (www.gotw.ca) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (www.gotw.ca/cpp_seminar). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.
Copyright (c) 2003 Herb Sutter