Click here to Skip to main content
15,890,185 members
Articles / Programming Languages / C#
Tip/Trick

Events and Delegates in Standard C++

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
10 Jan 2014CPOL2 min read 23.1K   9   5
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)


Written By
Software Developer (Senior)
Serbia Serbia
11 Years of development in various programming languages. Recently focused on .NET/Angular development.

Comments and Discussions

 
GeneralRight Idea for a Valuable Construct - More robust solutions are possible Pin
Paul M Watt13-Jan-14 11:41
mentorPaul M Watt13-Jan-14 11:41 
I think you have identified a very valuable construct and generally have the right idea for how to implement this.

I realize you are learning, and this is a first attempt to solve this problem. Just keep in mind C++ is such a versatile language with so many different possible solutions for a problem. Your solution is valid. The suggestions made so far simply point to a solution that is more robust and maintainable.

The idea that you present is simple yet very valuable. I think with a little playing around you should be able to convert this into a template solution fairly easily. I don't think you will require template meta-programming either.

One thing I would like to point out is the fixed array used to collect and manage your delegate instances. Your input += operator does not verify that it is safe to add more delegates passed 10 items.

C++
private:  \
        DELEGATE delegates[10]; \
        int current; \


Chances are the current param will be placed after the delegates array in memory, and some element after the end of range will eventually overwrite current. Since this is your counter, your application will most likely do something undefined in an undesirable way.

Consider modifying your solution to add delegate nodes in a linked list.

Like I said earlier, I think you have the right idea for a valuable construct that is not present by default in C++. I am OK with the use of MACROS, if they are used to simplify a declaration in a class definition or possibly a table declaration at the top of a source file.

However, I try to create solutions that allow developers to benefit from MACRO declarations, but never actually call them directly. Functionality like that can be placed in inline functions.

Keep with it.
Applying your experience from C# to your C++ development will really help you compared to developers moving from C to C++; especially if you avoid MACROS.
To know and not do, is not yet to know

http://www.codeofthedamned.com

GeneralRe: Right Idea for a Valuable Construct - More robust solutions are possible Pin
Slavisa13-Jan-14 13:54
Slavisa13-Jan-14 13:54 
QuestionI'm not sure ... Pin
Florian Rappl10-Jan-14 3:13
professionalFlorian Rappl10-Jan-14 3:13 
AnswerRe: I'm not sure ... Pin
Oded Arbel12-Jan-14 1:32
Oded Arbel12-Jan-14 1:32 
GeneralRe: I'm not sure ... Pin
Slavisa12-Jan-14 3:02
Slavisa12-Jan-14 3:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.