|
Introduction
Delegates and events are definitely cool features of .NET/C# and there are already many attempts to simulate them in standard C++. The Delegate class described in this article tries to provide a complete and easy to use solution for using delegates in standard C++. Your feedback is welcome.
Design Goals
There are already several good delegate implementations on CodeProject. However, there is still no solution good enough for broad developers. For example, some implementations do not support multicast delegates (which is a must for events), some implementations do not support function objects (functors), some implementations are hard to use (syntax). So the design goals of my Deletgate class are:
- Support all C++ callable entities, including static functions, member functions and functors.
- Support single-cast and multicast in one
Delegate class, so users don't have to implement multicast functionalities themselves.
- Easy to understand and use.
Examples
1. Using delegates#include "AcfDelegate.h"
using namespace Acf;
static void H() { ... }
class Foo {
public:
void G() { ... }
};
class Foo2 {
public:
void operator()(int n) { ... }
};
Delegate<void ()> a(&H);
assert(a == &H);
Foo foo;
Delegate<void ()> b(&foo, &Foo::G);
assert(b == std::make_pair(&foo, &Foo::G);
Delegate<void ()> c = a;
assert(c == &H);
Delegate<void (int)> d(Foo2());
a();
d(100);
d += &H;
d += std::make_pair(&foo, &Foo::G);
d -= std::make_pair(&foo, &Foo::G);
d -= &H;
2. Using eventsclass Button {
public:
Delegate<void ()> Click;
};
Button btn;
btn.Click += &F;
btn.Click += std::make_pair(&o, &MyObj::G);
btn.Click();
btn.Click -= std::make_pair(&o, &MyObj::G);
3. Using events (advanced)
You may want more control on how event handlers are managed and fired, for example, you care about thread safety. class Button {
private:
Delegate<void ()> click;
Mutex mutex;
public:
template <class T>
void add_Click(const T& h) {
ScopedLock lock(this->mutex);
this->click += h;
}
template <class T>
void remove_Click(const T& h) {
ScopedLock lock(this->mutex);
this->click -= h;
}
protected:
void OnClick() { if (this->click) this->click(); }
};
btn.add_Click(&F);
btn.add_Click(std::make_pair(&o, &MyObj::G));
Delegate Classnamespace Acf {
template <class TSignature>
class Delegate;
template <class R, class T1, class T2, ..., class TN>
class Delegate<R (T1, T2, ..., TN)> {
public:
Delegate();
template <class TFunctor>
Delegate(const TFunctor& f);
template <class TPtr, class TFunctionPtr>
Delegate(const TPtr& obj, const TFunctionPtr& mfp);
Delegate(const Delegate& d);
~Delegate();
public:
bool IsEmpty() const;
bool IsMulticast() const;
public:
template <class TFunctor>
void Add(const TFunctor& f);
template <class TPtr, class TFunctionPtr>
void Add(const TPtr& obj, const TFunctionPtr& mfp);
template <class TFunctor>
bool Remove(const TFunctor& f);
template <class TPtr, class TFunctionPtr>
bool Remove(const TPtr& obj, const TFunctionPtr& mfp);
void Clear();
public:
operator bool() const;
bool operator!() const;
template <class TFunctor>
Delegate& operator=(const TFunctor& f);
Delegate& operator=(const Delegate& d);
template <class TFunctor>
Delegate& operator+=(const TFunctor& f);
template <class TFunctor>
friend Delegate operator+(const Delegate& d, const TFunctor& f);
template <class TFunctor>
friend Delegate operator+(const TFunctor& f, const Delegate& d);
template <class TFunctor>
Delegate& operator-=(const TFunctor& f);
template <class TFunctor>
Delegate operator-(const TFunctor& f) const;
template <class TFunctor>
friend bool operator==(const Delegate& d, const TFunctor& f);
template <class TFunctor>
friend bool operator==(const TFunctor& f, const Delegate& d);
template <class TFunctor>
friend bool operator!=(const Delegate& d, const TFunctor& f);
template <class TFunctor>
friend bool operator!=(const TFunctor& f, const Delegate& d);
R operator()(T1, T2, ..., TN) const;
};
}
The TFunctor template parameter in the Delegate class supports static functions, member functions (via std::pair class that wraps an object pointer and a member function pointer) and functors. The TPtr template parameter for member functions support plain pointers (e.g. Foo*) and smart pointers (e.g. boost::shared_ptr<Foo>).
Notes
- You must enable runtime type information (RTTI) in your project in order to use the
Delegate class.
- The current implementation supports up to six function parameters, which should be enough for most applications (and in general it's a bad practice to have more than six parameters).
- There are no operators
== and != for comparing delegates, because they are impossible to implement correctly for functors (see boost.function).
- The current implementation is not optimized for performance. However, it should be OK for most applications.
History
- 12/24/2005: changed the exception behavior when an empty delegate is called - if the delegate return type is
void, then no exception will be thrown, otherwise an InvalidCallException will be thrown.
- 8/28/2005: initial release.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 23 of 23 (Total in Forum: 23) (Refresh) | FirstPrevNext |
|
 |
|
|
can the delegator call to different typedef of function? i mean same delegator that call different function having different number of parameter.
from, -= aLbert =-
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
can't seem to get static delegates to work. is this even possible?
-- modified at 15:21 Tuesday 27th February, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I tried to compile the source in linux using g++(4.0.2), but got some error, [zzq01@zhangzq71 Delegate]$ make g++ -c -o Delegate.o Delegate.cpp In file included from AcfDelegate.h:81, from Delegate.cpp:12: AcfDelegateTemplate.h:37:1: error: pasting "," and "typename" does not give a valid preprocessing token AcfDelegateTemplate.h:38:1: error: pasting "," and "T" does not give a valid preprocessing token AcfDelegateTemplate.h:66:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:75:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:103:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:201:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:202:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:366:1: error: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:367:1: error: pasting "," and "a" does not give a valid preprocessing token In file included from AcfDelegate.h:85, from Delegate.cpp:12:
it seems the preprocessor error, I tried another way, g++ -E Delegate.cpp > output.cpp g++ -o output output.cpp it can successfully create the executable file, and the result of the program is correct. Do you know what is the error when compiling with gcc?
Regards, ZhangZQ
email:zhangzq71@hotmail.com
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
I would find it very handy if the delegate had at least an option to not throw an exception when a delegate that has never been assigned is nonetheless invoked. This would avoid having to check .IsEmpty() each time one wants to signal an event and doesn't know if anyone is subscribed. One could add a dummy subscriber to each delegate but not being able to add a default value in the delegate declaration (as is possible in C#) you would have to add it in the containing class constructor and that would complicate matters unnecessarily. I've modified operator() for my personal use in the way listed below. Thanks for a great library.
Flavio.
#define ALLOW_EXECUTE_EMPTY #ifdef ALLOW_EXECUTE_EMPTY R OnEmptyDelegate() const { return R(); } #else R OnEmptyDelegate() const { throw InvalidOperationException(); } #endif R operator()(ACF_DELEGATE_FUNCTION_PARAMS) const { if (this->_last == NULL) OnEmptyDelegate();
if (this->_last->Previous != NULL) InvokeDelegateList(this->_last->Previous ACF_DELEGATE_COMMA ACF_DELEGATE_FUNCTION_ARGS); return this->_last->Invoke(ACF_DELEGATE_FUNCTION_ARGS); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks Flavio, the exception behavior is changed in the new version - when an empty delegate is called and the return type is void, no exception will be thrown. Yingle
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1. Your implementation is probably appropriate for most applications (especially GUI) although I doubt that class templates that use function signatures as template arguments can be 'Easy to understand and use'.
2. compiling your code with g++ 3.4 gives the following errors:
Compiling source file(s)... Delegate.cpp In file included from AcfDelegate.h:74, from Delegate.cpp:12: AcfDelegateTemplate.h:37:1: pasting "," and "class" does not give a valid preprocessing token AcfDelegateTemplate.h:38:1: pasting "," and "T" does not give a valid preprocessing token AcfDelegateTemplate.h:66:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:75:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:103:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:201:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:202:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:366:1: pasting "," and "a" does not give a valid preprocessing token AcfDelegateTemplate.h:367:1: pasting "," and "a" does not give a valid preprocessing token
... and many more
Your macros don't work as expected for g++ (don't know whether it's a g++ bug). The macro cascades can easily be avoided by using a simple Code Generator that does what the current macros are supposed to do.
It would be nice if you compared your implementation with other implementations (advantages/disadvantages, performance, ease of use, ...).
Best wishes, Roland Pibinger
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, Roland
Thanks for your comments. Here are my answers:
1. It's impossible to have a better syntax in current C++. The original syntax is Delegate0<R>, Delegate1<R, T1>, Delegate2<R, T1, T2>, .... However many people do not like it. The function signature is probably the best we can do (at least it has a clear separation of return value and parameters).
2. I did not try g++ 3.4, but on g++ 3.2.3 they are just warnings. Maybe you can avoid this by some compiler switch (not sure). I will look at this when I have time. (If you have better solution, don't hestitate to tell me)
3. People really asked about comparsion to other delegate implementations (boost.signal, or others delegate classes on CodeProject). It's a good topic, I will try when I update this article later.
Ying Le Jia
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi, Yingle!
Yingle Jia wrote: 2. I did not try g++ 3.4, but on g++ 3.2.3 they are just warnings. Maybe you can avoid this by some compiler switch (not sure). I will look at this when I have time. (If you have better solution, don't hestitate to tell me)
I don't know if it's a better solution but you could preprocess only the file with VC++, e.g.
cl /EP Delegate.cpp > out.txt
and then exchange the contents of AcfDelegate.h with the relevant portion of the preprocessor output (replace the macros with source code).
Best wishes, Roland Pibinger
-- modified at 13:42 Tuesday 6th September, 2005
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yingle Jia wrote: 1. It's impossible to have a better syntax in current C++. The original syntax is Delegate0, Delegate1, Delegate2, .... However many people do not like it. The function signature is probably the best we can do (at least it has a clear separation of return value and parameters).
Just realize that some compilers don't support this syntax (this is why Boost and other implementations for TR1 of STL offer 2 versions of the fuction template).
Yingle Jia wrote: 3. People really asked about comparsion to other delegate implementations (boost.signal, or others delegate classes on CodeProject). It's a good topic, I will try when I update this article later.
Boost is a good one to compare your implementation to since there is a strong liklihood that much of the work done by Boost will end up in the next version of the Standard C++ Libraries. If your implementation is more efficient and/or robust, you may want to tweak it a bit (to meet their coding standards) and submit it for review.
If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week
Zac
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, Zac
Thanks for your comments. Boost does have good signal and function libraries, and I learned something from them (e.g. the function signature syntax). However, my Delegate class has different design goals, e.g. I like it to be like C#/.NET and I don't like to mass the design (syntax) for old and silly compilers (like VC6). People can have their own choices.
Yingle
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I wouldn't call VC6 "old and silly". There are still plenty of companies that use it as their standard development environment (the company I work for is one of them ... and quite frankly, management has no immediate plans to upgrade ... unfortunately).
My points with regard to boost is simple: 1) many of the libraries in TR1 are boost derivitives and signals could very well make that list and 2) it is designed to be portable. If you know that there is a strong chance something will become a standard, it is worth your time to at least learn how to use it. If portability is not a concern (that is, you know for sure that you won't be switching compilers, ever), then you have nothing to worry about. I don't know many companies that can guarantee that, though. Just some things to keep in mind.
As a side note, creating libraries like this are excellent academic exercises and can only enhance your skills (and you did a pretty good job -- the exception-safe syncronization is great).
If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week
Zac
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Zac,
I quite understand your points. Yes, VC6 is still widely used (some people don't want to upgrade to VC7/7.1 because UI changed a lot, some people don't want to upgrade because they mistakenly think VC7/7.1 can only be used to write .NET programs). Actually when I first wrote the class, it supported the syntax Delegate0, Delegate1, Delegate2 ... like boost, which is portable and supports VC6. However, when I talked to people, many of them (I guess they are using VC7/7.1 or GCC like me ^_^) thought that syntax is ugly, and a big concern is that people will use that syntax everywhere in order to make their code "portable" (I see many people using boost.function are using function0, function1 everywhere instead of the prefered syntax). That's why I did not put VC6 in my must-support list. Of course this can change according to user feedback.
Yingle
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm VERY interested in the way this class makes use of variable template paramters. I have never before seen this (T1, T2, ..., TN) syntax. It's only available in VC7.1+, and very few UNIX C++ compilers. I notice BOOST makes use of it as well, but also has the old-fashioned Functor1 Functor2 Functor3 style syntax for backwards compilers.
Does anyone have a link that describes this syntax in detail? I've been unlucky so far with Google.
-- modified at 12:46 Thursday 1st September, 2005
One more question - Why does the article say it only supports functions with up to 6 parameters? Does that ...TN syntax only support 6 paramters?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
armentage,
Currently there is no direct variable template parameters support in standard C++ (though there is a proposal for C++0x). One has to use template (partial) specialization to achieve it. i.e.
template <class TSignature> class Delegate; // no body
template <class R> class Delegate<R ()> { };
template <class R, class T1> class Delegate<R (T1)> { };
template <class R, class T1, class T2> class Delegate<R (T1, T2)> { };
template <class R, class T1, class T2, class T3> class Delegate<R (T1, T2, T3)> { };
...
You see there is a compiler-time limit on the numbers of template parameters. Boost actually supports generating arbitary numbers using script language, but I think it's a good practice to keep function parameters no more than 6 - that's the reason.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
What I don't understand is how those (T1,T2,T3) work. Was that just pseudo code in your example? I've never seen any C++ like that other than your Delegate and the Boost signals library.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
armentage wrote: Currently there is no direct variable template parameters support in standard C++ (though there is a proposal for C++0x). One has to use template (partial) specialization to achieve it. i.e.
...
You see there is a compiler-time limit on the numbers of template parameters. Boost actually supports generating arbitary numbers using script language, but I think it's a good practice to keep function parameters no more than 6 - that's the reason.
This is partially correct. Standard C++ currently does support variable template parameters, but there are several compilers that do not fully support it. Additionally, even those that do, some of them do not support the function-style syntax (even though it is technically standard as well). The C++0x Standard is suppose to more formally define this area, but you can get compilers that are fully compliant in this area.
armentage wrote: What I don't understand is how those (T1,T2,T3) work. Was that just pseudo code in your example? I've never seen any C++ like that other than your Delegate and the Boost signals library.
It is real code, however, it is not supported by all compilers. If you take away the (), it is more portable at the moment. Boost has several libraries that use this feature (the function template is another example). You should note that currently, the Boost libraries offer 2 versions to support non-compliant compilers. I imagine that will change when C++0x is finalized.
If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week
Zac
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
 | cool! |  | Peifeng, Gu | 0:23 29 Aug '05 |
|
|
 |
|
|
 |
|
|
 |
|
|
I have not tried it yet, but my personal opinion on Boost is that it is designed for advanced C++ developers.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Hi Yingle, got my 5, i'm really impressed by your ACF. actually, boost::signal dose almost the same thing as you described here. IMHO, boost is designed by advanced c++ developers for all standard c++ users.
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
It takes only a short while to understand boost, be the best while work with the best, no worry!
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I am not sure about this. I find many parts of the boost documentation very hard to understand. I admit I have not read it recently, maybe things are better now.
John
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|