c++ delegates, evil method?

Started by
5 comments, last by Aressera 11 years ago

Hello all, been a while, I'm working on this project for a university assignment and I would really like

delegates in C++ haha, so I came up with something in a side test project and I'm pretty sure its

evil, could someone explain to me what could happen if it is in fact evil (ignoring the usual errors that could happen)

It compiles and runs correctly in visual studio 2012 without warnings

heres the code:


#include <iostream>
#include <list>

class object 
{ 
public:
	
};

struct eventargs { };

template <typename eventargs_t> struct delegate {
	typedef void (object::*type)(object*, eventargs_t*);
};

typedef delegate<eventargs>::type			eventhandler;

template <typename handler_t>
class event
{
private:
	std::list<handler_t>	_handlers;

public:
	template <typename handler_u> // I would like to see if the handler_u class is derived from 'object'
	void addhandler(handler_u handler) { _handlers.push_back((handler_t)handler); }

	template <typename handler_u> // I would like to see if the handler_u class is derived from 'object'
	void removehandler(handler_u handler) { _handlers.remove((handler_t)handler); }

	void invoke(object* sender, eventargs* args)
	{
		std::list<handler_t>::iterator it;
		for (it = _handlers.begin( ); it != _handlers.end( ); ++it)
			(sender->*(*it))(sender, args);
	}
};


class other : public object
{
public:
	void myhandler(object* sender, eventargs* args) { std::cout << "Hello World!" << std::endl; }
	void myhandler2(object* sender, eventargs* args) { std::cout << "Goodbye World!" << std::endl; }
};


int main(int, char**)
{
	object *a = new other( );
	event<eventhandler> e;
	
	e.addhandler(&other::myhandler);
	e.addhandler(&other::myhandler2);

	e.invoke(a, nullptr);
	e.removehandler(&other::myhandler);

	e.invoke(a, nullptr);

	return 0;
}

... Pretty sure its evil, but in what way?...

Just FYI I'm probably going to remove the templated parts of add/removehandler

Advertisement

Nice. One major problem I see is that you don't get the instance of an object there. You're basically invoking the handlers with a NULL pointer.

If you want some more tested delegates for c++ look for FastDelegate. It's working across all compilers I encountered.

I still wish I could make an add handler routine that doesn't need a wrapper, just accepts the method name (as in C#)

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

What do delegates do that (member)function pointers don't?

Hmm, the cast to handler_t looks really fishy to me. You essentially call a function on object* pointers that are potentially only defined for one subclass of object... Also why are you passing the object both as argument AND this pointer? Seems redundant to me?

Anyway C++ (11 or boost) has nice ways to handle "callable stuff" (including member functions) in the form of std::function/boost::function:


#include <iostream>
#include <list>
#include <functional>

template <typename object_t, typename arguments_t>
class event
{
private:
        typedef std::function<void(object_t*, arguments_t*)> handler_t;
        std::list< handler_t >  handlers;
 
public:
        template <typename handler_u>
        void addhandler(handler_u handler) { handlers.push_back(handler_t(handler)); }
 
        void invoke(object_t* sender, arguments_t* args)
        {
                for (auto it = handlers.begin( ); it != handlers.end( ); ++it)
                        (*it)(sender, args);
        }
};
 
struct eventargs { };
 
class other
{
public:
        void myhandler(eventargs* args) { std::cout << "Hello World!" << std::endl; }
        void myhandler2(eventargs* args) { std::cout << "Goodbye World!" << std::endl; }
};
 
 
int main(int, char**)
{
        other *a = new other( );
        event<other, eventargs> e;
       
        e.addhandler(&other::myhandler);
        e.addhandler(&other::myhandler2);
 
        e.invoke(a, nullptr);
 
        delete a;
        return 0;
}

If you have VS2012 and can install latest CTP, it adds support to variadic templates (from C++11). You can then do stuff like this:



#include <functional>
#include <iostream>
#include <list>

template<typename ... Args>
struct event {
	std::list<std::function<void (Args...)>> handlers;
	void listenIn(const std::function<void (Args...)> & handler) {
		handlers.push_back(handler);
	}
	void call(const Args ... args) {
		for (auto handler : handlers) {
			handler(args ...);
		}
	}
};

class other {
public:
	void handler1(const std::string & name) { std::cout << "Hello " << name.c_str() << "!" << std::endl; }
};

int main(int argc, char* argv[])
{
	auto a = new other();

	// this event will work with handlers having one string parameter
	event<std::string> e;

	// listen in any compatible class method
	e.listenIn(std::bind(
		&other::handler1, // class member
		a, std::placeholders::_1 // bind first param to object pointer, leave others for the event
	));

	e.call("World");

	delete a;

	return 0;
}

Basically you get type safety with no downsides, if you can ignore crap in the bind method. You can also add a lambda function as a listener:


// listen in inline lambda function
e.listenIn([] (std::string & name) { 
	std::cout << name.c_str() << " is mine." << std::endl; 
});

Several parameters example:


class other {
public:
	void handler1(const std::string & name) { std::cout << "Hello " << name.c_str() << "!" << std::endl; }
	void handler2(int number, int basket) { 
		std::cout << "There are " << number << " apples in the basket " << basket << std::endl; 
	}
};

int main(int argc, char* argv[])
{
	auto a = new other();

	// this event will work with int, int
	event<int, int> e2;

	e2.listenIn(std::bind(
		&other::handler2, // class
		a, std::placeholders::_1, std::placeholders::_2 // bind first param to object pointer, leave others for the event
	));

	e2.call(8, 2);

	delete a;

	return 0;
}

Note that adding listener in lambda function is actually safer and easier, because all the hell will break loose if the event is called after the "a" is deleted (on access to any class member).

Hello all, been a while, I'm working on this project for a university assignment and I would really like
delegates in C++ haha, so I came up with something in a side test project and I'm pretty sure its
evil, could someone explain to me what could happen if it is in fact evil (ignoring the usual errors that could happen)

Delegates per se are not evil. There are plenty of implementations out there, with various pros and cons.

Your particular implementation has a pretty nasty fault, that's also very easy to spot: The c-style cast to "handler_t". Pretty much anything can happen at that point. From compiler errors to obvious runtime errors to subtle runtime errors.

As a general rule, when programming C++, stick to static_cast and dynamic_cast. When interfacing with legacy code or low level, use reinterpret_cast and const_cast. Never ever use c-style cast, especially if you only put it in there to shut the compiler up.

http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible this is by far the best argumentation and implementation of delegates for C++. I know of one engine that has shipped games with this particular piece of code.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

This is my preferred way to doing delegates in C++, using function objects (std::function, boost::function or roll-your-own). It's definitely the least intrusive pattern I've found and I use it extensively in my GUI system. It's silly to have to define a new class just to receive events for another... This design keeps both engine and user code clean, simple, and relatively efficient.


class ButtonDelegate
{
    public:
    
    Function<void ( Button& )> press;
    Function<void ( Button& )> release;
    void* userData; // optional, could use boost::any or similar
}

void press( Button& button )
{
}

void release( Button& button )
{
}

void main()
{
    Button* b = new Button();
    
    ButtonDelegate delegate;
    delegate.press = press;
    delegate.release = release;
    
    b->setDelegate( delegate ); // could potentially have more than one delegate object if you want
}

// Button.cpp

void Button:: handlePress()
{
    if ( delegate.press )
        delegate.press( *this );
}

This topic is closed to new replies.

Advertisement