Skip to content

Instantly share code, notes, and snippets.

@kahunamoore
Forked from audioplastic/nicks_observer.cpp
Last active August 29, 2015 14:15
Show Gist options
  • Save kahunamoore/2fd2411620658e16df40 to your computer and use it in GitHub Desktop.
Save kahunamoore/2fd2411620658e16df40 to your computer and use it in GitHub Desktop.
// C++11 style observer pattern implementation using lambda for scoped
// connection management.
//
// Copyright (c) 2012 Nick C.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <iostream>
#include <list>
#include <functional>
class EventBroadcaster;
class EventListener;
//Connection class
class Connection
{
public:
Connection(std::function<void ()> _disconnect_handler) :disconnect_handler(_disconnect_handler) {}
//Handle the disconnect housekeeping when the connection is deleted
~Connection(){disconnect_handler();}
private:
std::function<void ()> disconnect_handler;
};
// Pure virtual listener base class declartion
class EventListener
{
public:
virtual void eventCallback(EventBroadcaster*) =0;
void connect(EventBroadcaster& b); // Some nicer syntax
void disconnect();
bool isConnected();
private:
std::unique_ptr<Connection> c = nullptr; //C++11 allows these to be set without initializer list
void connect(Connection* _c); // This connection syntax is just to ugly and is set to private because of it!!
};
// Broadcaster base class
class EventBroadcaster
{
public:
~EventBroadcaster()
{
removeAllEventListeners(); //sever all connections on delete
}
Connection* addEventListener(EventListener* l_ptr)
{
// Check to see if listener already added
if( !binary_search (listenerList.begin(), listenerList.end(), l_ptr) )
{
listenerList.push_back(l_ptr);
//Need to capture by making a copy [=] - wacky things happened with [&] - l_ptr gets munglerated
return new Connection([=](){this->removeEventListener(l_ptr);});
} else {
return nullptr;
}
}
void removeEventListener(EventListener* l_ptr)
{
// This fololwing command informs the connection object of the disconnect
// We don't get into an infinite loops because the connection is already null after the first call
l_ptr->disconnect();
listenerList.remove(l_ptr);
}
void removeAllEventListeners()
{
// Can't use clear because we need to tell each listener it has been disconnected
// Can't use a range-based for on a container with a changing size!
while(listenerList.size()>0){
removeEventListener(listenerList.back());
}
}
void notify()
{
for(auto l_ptr : listenerList) {
if(l_ptr){l_ptr->eventCallback(this);}
}
}
private:
std::list<EventListener*> listenerList;
};
//Event listener defninitions
void EventListener::connect(Connection* _c)
{
if(_c) {// Need this if statement or we get a nullptr if this connection already registered
disconnect(); // Sever any previous connection (these listeners can only observe one object)
c.reset(_c);
}
}
void EventListener::connect(EventBroadcaster& b) // Some nicer syntax
{
connect(b.addEventListener(this));
}
void EventListener::disconnect()
{
if(c) {c.reset(nullptr);} // No need to null a null
}
bool EventListener::isConnected()
{
return (c!=nullptr);
}
//========================================================
// Simple demo stuff from here down
//========================================================
// DEMO Notifier is an event broadcaster
class Notifier : public EventBroadcaster
{
public:
int get() const {return i;};
void set(int _i) {
i=_i;
notify();
};
private:
int i = 0; //C++11 nicety
};
// DEMO Responder is an EventListener
class Responder : public EventListener
{
public:
Responder(std::string _name = std::string()) : name(_name){}
~Responder()
{
std::cout << "Responder "<< name <<" deleted" << std::endl;
}
void eventCallback(EventBroadcaster* b_ptr)
{
auto Notifier_ptr = static_cast<Notifier*>(b_ptr);
std::cout << name << " notified that value is " << Notifier_ptr->get() << std::endl;
}
private:
std::string name;
};
// Main function purely for demonstration
int main (int argc, char const *argv[])
{
Notifier n;
Responder r1("r1");
// Test #1 connect and signal
r1.connect(n);
n.set(5);
// Test #2 disconnect at the responder end
r1.disconnect();
n.set(7);
// Test #3 reconnect multiple times
r1.connect(n);
r1.connect(n);
r1.connect(n);
n.set(9999999);
// Test #4 disconnect at the broadcaster
n.removeEventListener(&r1);
n.removeEventListener(&r1); //Check it can be done more than once
n.set(7);
// Test #5 multiple listeners
Responder r2("r2"), r3("r3");
r3.connect(n);
r2.connect(n);
r1.connect(n.addEventListener(&r1)); //Can still use this ugly syntax if you want
n.set(321);
n.removeAllEventListeners();
n.set(2);
// Test #6 multiple listeners again after a remove all
r3.connect(n);
r2.connect(n);
r1.connect(n);
n.set(4444);
// Test #7 this is the cool one showing automatic scoped connection management
{
std::unique_ptr<Responder> r( new Responder("r_heap") );
r->connect(n);
n.set(11);
n.set(55);
}
n.set(88888888); //No heap listener (lambda magic at work)
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment