Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Events and Delegates in Standard C++

0.00/5 (No votes)
10 Jan 2014CPOL2 min read 24K  
Events and Delegates in Standard C++ implemented using Macro functions

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:

  1. RET_TYPE - return type of a function referenced by the delegate
  2. DELEGATE - name of newly defined delegate type
  3. PARAMS - argument list with following format - (type arg, type2 arg2...)
  4. 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:

C++
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:

C++
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; //this is correct too, class is declared by macro function delegate

eventZero.invoke();
threeArgEvent.invoke(667, "Breaking bad", new User());

Attach multiple delegates:

C++
#include "stdafx.h"  
#ifndef EVENTS_DELEGATES
#include "EvDelegates.h"
#endif
  
//Test model
struct User{
	int id;
	char * name;
	long years;
};

//User repository mock
class UsersDb
{
public:
	delegate3(void, USER_ADDED_DELEGATE, int , 
	id, char *, sName, long, years);  //declare delegate with three arguments

	EVENT_USER_ADDED_DELEGATE onUserAdded;  //actual event to which listeners will be attached

	//add new user, notify listeners
	void addUser(User u) { 
		//TODO: add user to db, yeah right!

		//notify all
		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;  //users repository

	//add listeners 
	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};

	//add users, and notify listeners
	te.addUser(u);
	te.addUser(u1);
	te.addUser(u2); 
	 
	return 0;
}

This should be the output:

VB
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. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)