Introduction
An event (in C#, ActionScript...) is a mechanism for a class to provide notifications to listeners when something notably happens to an object.
Events are declared using delegates, which are basically very similar to a C/C++ function pointers. Delegates are method wrappers that can be passed to a code which can invoke wrapped method without any compile-time knowledge of which method will be invoked actually.
This tip explains an idea of implementing Events and Delegates in Standard C++, with focus on syntax.
Before going any further, you have to know that this is a tip/idea made by a C++ beginner. I have approximately 3 months of C++ professional experience and haven't implemented anything in CPP for quite a long time (5 years actually), so please, treat this tip just as an idea and as my desire to hear suggestions and comments from more experienced C++ developers on what are the positive and negative things with this approach.
Implementation
Objectives:
- Implement easy to use eventing mechanism
- Obtain syntax similar to C#
- Learn C++ :)
I've tried to keep the code small and simple because this is just an idea. So, the implementation contains only one header file named EvDelegates.h:
#ifndef EVENTS_DELEGATES
#define EVENTS_DELEGATES
#include "stdafx.h"
#define delegate(RET_TYPE, DELEGATE, PARAMS, PARAM_NAMES) \
typedef RET_TYPE (*DELEGATE) PARAMS; \
class EVENT_##DELEGATE{ \
private: \
DELEGATE delegates[10]; \
int current; \
public: \
EVENT_##DELEGATE(): current(0){} \
EVENT_##DELEGATE & operator +=(DELEGATE func) { delegates[current++] = func; return *this; } \
void invoke PARAMS { for (int __DELEGATE##INDEX = 0;
__DELEGATE##INDEX < current; __DELEGATE##INDEX++) (*delegates[__DELEGATE##INDEX ])PARAM_NAMES; } \
};
#define delegate0(RET_TYPE, DELEGATE) \
delegate(RET_TYPE, DELEGATE, (), ())
#define delegate1(RET_TYPE, DELEGATE, t, a) \
delegate(RET_TYPE, DELEGATE, (t a), (a) )
#define delegate2(RET_TYPE, DELEGATE, t, a, t2, a2) \
delegate(RET_TYPE, DELEGATE, (t a, t2 a2), (a, a2) )
#define delegate3(RET_TYPE, DELEGATE, t, a, t2, a2, t3, a3) \
delegate(RET_TYPE, DELEGATE, (t a, t2 a2, t3 a3), (a, a2, a3) )
#define delegate4(RET_TYPE, DELEGATE, t, a, t2, a2, t3, a3, t4, a4) \
delegate(RET_TYPE, DELEGATE, (t a, t2 a2, t3 a3, t4 a4), (a, a2, a3, a4) )
#define delegate5(RET_TYPE, DELEGATE, t, a, t2, a2, t3, a3, t4, a4, t5, a5) \
delegate(RET_TYPE, DELEGATE, (t a, t2 a2, t3 a3, t4 a4, t5 a5), (a, a2, a3, a4, a5) )
#define event(DELEGATE, EVENT_NAME) \
EVENT_##DELEGATE EVENT_NAME;
#endif
So, what we have here.
Macro functions:
- delegate
- delegateX (X = 0 .... )
- event
The first macro function delegate is the most important.
Parameters:
RET_TYPE
- return type of a function referenced by the delegateDELEGATE
- name of newly defined delegate typePARAMS
- argument list with following format - (type arg, type2 arg2
...) PARAM_NAMES
- argument list without argument types - (arg, arg2
)
This method basically does two things. First, it defines new type named DELEGATE
, pointer to a function with return type RET_TYPE
and function arguments passed as PARAMS
argument. Second, it creates event class that contains logic for attaching multiple delegates of previously defined delegate type, and invoke method with the same argument list as attached delegates.
Class created using this method gets class name created as concatenation of string EVENT_
and value of argument DELEGATE
.
There are a lot of Delegate-Event C++ implementations, vast majority is using templates classes, type trairs and so on... many of this mechanisms can be found inside boost library. The goal which I tried to achieve is to reduce manual declaration of template event classes for various argument lists, so I left that job to a macro function.
Second group of macro functions are just wrappers around function delegate
, and have nicer and easier to use syntax.
Macro function event
is used for declaring instance of an event for given delegate name.
Using the Code
To try this out, you should create header file containing code listed in previous section. This code was developed using Microsoft Visual Studio 2010.
Declaring delegates using first macro function - Weird way:
delegate (void, WIERD_DELEGATE_WITHOUT_PARAMS, (), ());
delegate (void, WIERD_DELEGATE, (int k, User & user), (k, user));
event(WIERD_DELEGATE_WITHOUT_PARAMS, e1);
event(WIERD_DELEGATE, e2);
e1.invoke();
e2.invoke(666, user);
A bit more user friendly delegate declaration - not so weird way:
delegate0(void, ZERO_ARGUMENT_DELEGATE);
delegate3(int, THREE_ARGUMENT_DELEGATE, long, l, char*, str, User *, ptrUser);
event(ZERO_ARGUMENT_DELEGATE, eventZero);
EVENT_THREE_ARGUMENT_DELEGATE threeArgEvent;
eventZero.invoke();
threeArgEvent.invoke(667, "Breaking bad", new User());
Attach multiple delegates:
#include "stdafx.h"
#ifndef EVENTS_DELEGATES
#include "EvDelegates.h"
#endif
struct User{
int id;
char * name;
long years;
};
class UsersDb
{
public:
delegate3(void, USER_ADDED_DELEGATE, int ,
id, char *, sName, long, years);
EVENT_USER_ADDED_DELEGATE onUserAdded;
void addUser(User u) {
onUserAdded.invoke(u.id, u.name, u.years);
};
};
void userDbListener1(int id, char * str, long years)
{
cout << "Id: " << id << endl;
cout << "Name: " << str << endl;
cout << "Years: " << years << endl;
}
void userDbListener2(int id, char * str, long years)
{
cout << "User [" << str << "] with id
[" << id << "] is ["<< years << "] years old." << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
UsersDb te;
te.onUserAdded += UsersDb::USER_ADDED_DELEGATE(&userDbListener1);
te.onUserAdded += &userDbListener2;
User u = {1, "Slavisa", 29};
User u1 = {2, "Marija", 19};
User u2 = {3, "Viktor", 44};
te.addUser(u);
te.addUser(u1);
te.addUser(u2);
return 0;
}
This should be the output:
Id: 1
Name: Slavisa
Years: 29
User [Slavisa] with id [1] is [29] years old.
Id: 2
Name: Marija
Years: 19
User [Marija] with id [2] is [19] years old.
Id: 3
Name: Viktor
Years: 44
User [Viktor] with id [3] is [44] years old.