bookmark - Basic C++ Event Handler Class If you're familiar with C# event handlers, this is a C++ mimic of

Basic C++ Event Handler Class - If you're familiar with C# event handlers, this is a C++ mimic of

 
 Discussion by FurryHead with 1 Replies.
 Last Update: April 10, 2011, 5:37 pm
 
bookmark - Basic C++ Event Handler Class If you're familiar with C# event handlers, this is a C++ mimic of  
    
free web hosting
 
Hey everyone, I'm going to teach you how to make a simple event handler in C++.

If you happen to be familiar with C#'s event handler system, this is going to mimic that.

I got the idea from a tutorial on how to make this same class in Python, but I couldn't find anything comparable to it in C++. Plus, it might be useful to someone...

Anyway, on to the code. I would recommend basic knowledge of generic classes, operator overloading, and the concept of function pointers. If you don't happen to know these, I will offer a really basic explanation along the way.

Note: All of this will be done from within Code::Blocks, it should be relatively easy to follow along with any other environment though.

So, to start with we will create two files - One header for the event handler class, one for the test program. Name them 'event.h' and 'main.cpp'. I would use a separate file for the event handler declarations and definitions, but with generics you can't do that. Unlike with a non-generic class, it will not give you linkage errors if you put the definitions in the header because of the way it compiles.

Now, in order to keep track of all the listeners to add we will be using a vector of function pointers. More on this later. So we will add the following skeleton code into event.h:

CODE

#include <vector>
#include <algorithm> // For the remove listener function so we can use std::remove_if

template <class Data> class Event {
typedef void (*fptr)(Data);
typename std::vector<fptr> listeners;

public:
// nothing yet!
};


Alright, now let's go through this line by line (after the includes)

template <class Data> class Event {
This line says that this is a generic class, and to define "Data" as whatever type is passed to us when the user creates a instance of this class. So, if we made a instance of this class as "Event<string> myEventHandler;" it would define Data as type string. If you want more detailed information about generics, see a C++ book or a tutorial or google it.

typedef void (*fptr)(Data);
This (basically) defines the type "void (*)(Data)" to fptr, so when we use "fptr myPtr;" we are declaring (a lot easier) a pointer to a function returning void taking a parameter of type "Data".

typename std::vector<fptr> listeners;
Creates a vector of function pointers. The typename keyword is sometimes needed to tell the compiler that 'Data' in the fptr typedef is a template name, not a object name - big difference, especially since g++ will spew unreadable errors about it.

Well, we have a basic skeleton now. Now we will add the operators!
Add these two functions right after the public: line and before the last };

CODE

Event& operator+=(fptr func) {
return add(func);
}
Event& operator-=(fptr func) {
return sub(func);
}
void operator()(Data& eventData) {
changed(eventData);
}


Well, each of those are pretty much the same - they delegate to other functions.
Read any tutorial on operator overloading for information on the first two... But I must explain the third one a little.

The third one is really neat, it's called a "Functor" - basically, look at the following code:

CODE

Event handler; // assuming we have finished the class
handler.add(function_name); // These two lines
handler += function_name; // have the exact same effect (the same applies to sub and -=)

//here's the neat functor
handler("Some new data");
handler.changed("Some new data");

The last two lines accomplish the exact same thing! I suppose this is why the docs call functors "Functions on steroids" ;) See google for more functor information.

Anyway, we now need to define the other 3 functions - add, sub, and changed. We'll start with changed():

CODE

void changed(Data& eventData) {
for (typename std::vector<fptr>::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) {
(*iter)(eventData);
}
}

Well, assuming you know at least the basics of C++ you will know what the first line does. I'll go over the other two lines, though:

for (typename std::vector<fptr>::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) {
If you've ever iterated over a vector, it's the same except for the typename keyword. It tells the compiler that Data is a template name not a object name. But wait, where did we say Data in this line? Well, we didn't. We said it in the typedef for fptr; Therefore it implies that it's here as well. Try compiling it without the typename, and good luck trying to understand the errors.

(*iter)(eventData);
This is the interesting part. To call a function through it's pointer, you can either use "fptr func_ptr; func_ptr =...; func_ptr();" as if it's a normal function, but the more common use is "(*func_ptr)()" because it explicitly tells the reader that it's calling a pointer to a function. Since we are using a iterator, that adds another level of misdirection - Technically, we could also use "(**iter)(eventData)" because one * de-references the iter pointer to the original function pointer, then the second * de-references it to the function itself. In short, iter is now a pointer to a pointer to a function, and the function can be called as "(**iter)(eventData)" OR (*iter)(eventData)". Read up on function pointers for more details.

Now for the other two functions:

CODE

Event& add(fptr func) {
for (typename std::vector<fptr>::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) {
if (*iter == func) // we must dereference the pointer-to-pointer-to-function to get the pointer-to-function so that we can compare it
// Case was true, the function has already been added so we return and don't add it again
return *this;
}
// Ok, func has not yet been added (or added then removed) so we can add it
listeners.push_back(func);
return *this;
}
// helper function for std::remove_if
bool evt_equal(fptr func1, fptr func2) {
return (func1 == func2);
}
Event& sub(fptr func) {
// This line removes all occurrences of func in the listeners list, if any.
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), evt_equal), listeners.end());
return *this;
}

Well, most of it is commented and explains itself, but I will talk about returning the "this" pointer for a moment.

The special keyword "this" is a pointer to the class instance that you're working from. When you say "return this;" it is returning a pointer - which is not good, as operators should return a reference to the object so that it is assignable to a non-pointer variable. So we de-reference the this pointer, thus returning the Event object in a way that is convenient to assign.

Now, all the code put together in event.h is as follows:

CODE

#include <vector>
#include <algorithm>

template<class Data> class Event {
typedef void (*fptr)(Data);
std::vector<fptr> listeners;

public:
void changed(Data& eventData) {
for (typename std::vector<fptr>::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) {
(*iter)(eventData);
}
}
Event& add(fptr func) {
for (typename std::vector<fptr>::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) {
if (*iter == func)
return *this;
}
listeners.push_back(func);
return *this;
}
bool evt_equal(fptr func1, fptr func2) {
return (func1 == func2);
}
Event& sub(fptr func) {
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), evt_equal), listeners.end());
return *this;
}

// operators
Event& operator+=(fptr func) {
return add(func);
}
Event& operator-=(fptr func) {
return sub(func);
}
void operator()(Data& eventData) {
changed(eventData);
}
};

Looking good, eh? Well, now we can test it out!!! :lol:

Here's the code for main.cpp, the comments will explain it:

CODE

#include <string>
#include <iostream>
#include "event.h"
using namespace std;

void onEvent(string data) {
cout << "Handler function called with: " << data << endl;
}

void onEvent2(string data) {
cout << "Handler function number 2 called with: " << data << endl;
}

int main() {
Event<string> handler; // Create an instance of Event, passing the string type as "Data" in the class
handler.add(onEvent); // Pass the onEvent function to the Event class, telling it to notify the
// function when something changes. also same as handler += onEvent;

string msg = "My handler works!"; // make a message
handler.changed(msg); // tell the event class that something changed, including the data -
// update all the listener functions! also same as handler(msg);

handler += onEvent2; // same as handler.add(onEvent2);

msg += " (revisited!)"; // add new data
handler(msg); // same as handler.changed(msg);
}

Now just compile it and have fun!!! :lol:

One thing I want to comment about functors and function pointers is that they are interchangeable. If you define a class with a functor, you can use it as if it were a function pointer! See functor docs for more information.

Any questions just post, I'll try to monitor this tutorial.

Sat Apr 9, 2011    Reply    New Discussion   


Sorry, I wrote the class declaration wrong. The data members should be "protected:" and not defaulted to private, so that it will be subclass-able.

Change the top of the "class Event" declaration as follows:

CODE

template<class Data> class Event {
protected: // protected so that subclasses can inherit the data
typedef void (*fptr)(Data);
std::vector<fptr> listeners;

// the rest of code...


Sorry!

Sun Apr 10, 2011    Reply    New Discussion   

Quickly Post to Basic C++ Event Handler Class If you're familiar with C# event handlers, this is a C++ mimic of  w/o signup Share Info about Basic C++ Event Handler Class If you're familiar with C# event handlers, this is a C++ mimic of  using Facebook, Twitter etc. email your friend about Basic C++ Event Handler Class If you're familiar with C# event handlers, this is a C++ mimic of Print
Reply / Comment Ask a Question? Share / Bookmark E-Mail a Friend Print

How To Draw A MAp using opengl dev c++ Programming In Glut (lesson 2)  How To Draw A MAp using opengl dev c++ Programming In Glut (lesson 2) (0)