Channels ▼
RSS

C/C++

Generalizing Observer


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 functions, with the caveat that as long as function doesn't provide equality comparison we still can't ignore duplicates when adding functions to a multicast set, and we can't remove functions 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 of bool 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 a container<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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video