C++11 observer pattern (signals, slots, events, change broadcaster/listener, or whatever you want to call it)

learnvst picture learnvst · Nov 27, 2012 · Viewed 20.4k times · Source

With the changes made in C++11 (such as the inclusion of std::bind), is there a recommended way to implement a simple single-threaded observer pattern without dependence on anything external to the core language or standard library (like boost::signal)?

EDIT

If someone could post some code showing how dependence on boost::signal could be reduced using new language features, that would still be very useful.

Answer

xtofl picture xtofl · Nov 27, 2012

I think that bind makes it easier to create slots (cfr. the 'preferred' syntax vs. the 'portable' syntax - that's all going away). The observer management, however, is not becoming less complex.

But as @R. Martinho Fernandes mentions: an std::vector<std::function< r(a1) > > is now easily created without the hassle for an (artificial) 'pure virtual' interface class.


Upon request: an idea on connection management - probably full of bugs, but you'll get the idea:

// note that the Func parameter is something
// like std::function< void(int,int) > or whatever, greatly simplified
// by the C++11 standard
template<typename Func>
struct signal {
  typedef int Key; // 
  Key nextKey;
  std::map<Key,Func> connections;

  // note that connection management is the same in C++03 or C++11
  // (until a better idea arises)
  template<typename FuncLike>
  Key connect( FuncLike f ) {
     Key k=nextKey++;
     connections[k]=f;
     return k;
  }

  void disconnect(Key k){
     connections.erase(k);
  }

  // note: variadic template syntax to be reviewed 
  // (not the main focus of this post)
  template<typename Args...>
  typename Func::return_value call(Args... args){
     // supposing no subcription changes within call:
     for(auto &connection: connections){
        (*connection.second)(std::forward(...args));
     }
  }
};

Usage:

signal<function<void(int,int)>> xychanged;

void dump(int x, int y) { cout << x << ", " << y << endl; }

struct XY { int x, y; } xy;

auto dumpkey=xychanged.connect(dump);
auto lambdakey=xychanged.connect([&xy](int x, int y){ xy.x=x; xy.y=y; });

xychanged.call(1,2);