|
It's hard to follow it, since of the original code much is gone. Mainly <'s being interpreted by the browser. But from what I can tell it's a nice addition. I'll keep this in mind until the next time I get to do some work on the delegates. I'm planning on using them in a project of mine, but I have not yet begun that coding part. When I get there, I'll revise the code and add your proposed changes (and update the article of course )
boutblock wrote:
Is there anyone here interested for adding multi-param to this already tricky stuff ?
What exactly do you mean by multi-param? Variable arguments being packed into an object[] array? That doesn't really fit into C++, mainly because there is no object nor any multi-param language construct. Well, there is, but it's the good old C way of doing things ... . I don't see how you could forward such parameters without making assumptions on how the compiler generates code for function calls (evil! ).
--
Shine, enlighten me - shine
Shine, awaken me - shine
Shine for all your suffering - shine
|
|
|
|
|
About multi params as object[], of course not
I just meant to insert what Mike Junkins already did to your code ! hehe
Now you're talking about weird ideas dealing with the way compilers work, I've got one. I'd like to be able to use those delegates as one can in C#, that is, passing the 'this' pointer as the pointed object implicitly.
I tried and failed with a mix of templates and inheritage. Only thing i found as close as possible was to define a new method of my this object to encapsulate the delegate construction (poor result
|
|
|
|
|
boutblock wrote:
I'd like to be able to use those delegates as one can in C#, that is, passing the 'this' pointer as the pointed object implicitly.
For that you'll have to tweak the compiler I'd say.
--
Shine, enlighten me - shine
Shine, awaken me - shine
Shine for all your suffering - shine
|
|
|
|
|
I forgot about implementing copy constructors and assignment operator for the delegates . They should pay respect to the reference counting..
I'll fix it when I get home.
--
Iron Maiden (Harris/Gers) wrote:
The rebel of yesterday, tomorrow's fool
Who are you kidding being that cool?
|
|
|
|
|
Jörgen,
Nice article, fun to read about interesting ways to use templates.
I downloaded the code and started experimenting with ways to remove the heap allocation. Old style C casts (C++ cast operator) seems to do the trick. I've only tested it by using the example you sent (release and debug builds) however so there might be some bugs lurking...
Anyway, here's a new delegate class that consolidates all the behavior into one class. It uses a templated static method in a nested class to 'save' the behavior required to do the proper cast.
template <typename PARAM_TYPE, typename RESULT_TYPE>
class delegate2
{
typedef delegate2<PARAM_TYPE,RESULT_TYPE> SELF;
typedef RESULT_TYPE (SELF::*pfMethodType)(PARAM_TYPE);
typedef RESULT_TYPE (*pfStaticInvokeFunc)(void* pObj, pfMethodType pFunc, PARAM_TYPE param);
template< typename TYPE, typename PARAM_TYPE, typename RESULT_TYPE >
class SaveClassType
{
typedef RESULT_TYPE (TYPE::* pfDelegateFunc)(PARAM_TYPE);
public:
static RESULT_TYPE Invoke(void* pObj, pfMethodType pFunc, PARAM_TYPE param)
{
return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))(param);
}
};
public:
template <typename TYPE, typename PARAM_TYPE, typename RESULT_TYPE>
delegate2(TYPE* pObj, RESULT_TYPE (TYPE::* method)(PARAM_TYPE)):
m_pObj(pObj),
m_pFunc((pfMethodType)(method))
{
m_pfInvoke = SaveClassType<TYPE,PARAM_TYPE,RESULT_TYPE>::Invoke;
}
RESULT_TYPE operator()(PARAM_TYPE param)
{
assert(m_pfInvoke);
return (*m_pfInvoke)(m_pObj,m_pFunc,param);
}
private:
void* m_pObj;
pfMethodType m_pFunc;
pfStaticInvokeFunc m_pfInvoke;
};
It is also possible to create a class that'll do multiple parameters or rather variable parameters. But I'm not sure it's worth it because of the type safety issues it puts into play. It might be possible to code around that as well but I didn't have the time to mess with it...
Still here's a way to do it that relies on incomplete template instantiation - the complier won't add the template methods we don't use. That and yet more casting. I only added the possibility for 1 or 2 params, but zero..n could be added.
template< typename RESULT_TYPE >
class delegate_ex
{
typedef RESULT_TYPE (delegate_ex<RESULT_TYPE>::*pfMethodType)();
typedef RESULT_TYPE (*pfStaticInvokeFunc)(void* pObj,
pfMethodType pFunc,
void* param);
typedef RESULT_TYPE (*pfStaticInvokeFunc2)(void* pObj,
pfMethodType pFunc,
void* param1,
void* param2);
template< typename TYPE,
typename RESULT_TYPE ,
typename PARAM_TYPE >
class SaveClassType
{
typedef RESULT_TYPE (TYPE::* pfDelegateFunc)(PARAM_TYPE);
public:
static RESULT_TYPE Invoke(void* pObj,
pfMethodType pFunc,
void* param)
{
return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))
(*static_cast<PARAM_TYPE*>(param));
}
};
template< typename TYPE,
typename RESULT_TYPE ,
typename PARAM_TYPE,
typename PARAM_TYPE2 >
class SaveClassType2
{
typedef RESULT_TYPE (TYPE::* pfDelegateFunc)
(PARAM_TYPE,PARAM_TYPE2);
public:
static RESULT_TYPE Invoke(void* pObj,
pfMethodType pFunc,
void* param1,
void* param2)
{
return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))
(*static_cast<PARAM_TYPE*>(param1),
*static_cast<PARAM_TYPE2*>(param2));
}
};
public:
template <typename TYPE,
typename RESULT_TYPE,
typename PARAM_TYPE>
delegate_ex(TYPE* pObj, RESULT_TYPE (TYPE::* method)(PARAM_TYPE)):
m_pObj(pObj),
m_pFunc((pfMethodType)(method))
{
m_pfInvoke = SaveClassType<TYPE,
RESULT_TYPE,
PARAM_TYPE>::Invoke;
}
template <typename TYPE,
typename RESULT_TYPE,
typename PARAM_TYPE,
typename PARAM_TYPE2>
delegate_ex(TYPE* pObj,
RESULT_TYPE (TYPE::* method)(PARAM_TYPE,PARAM_TYPE2)):
m_pObj(pObj),
m_pFunc((pfMethodType)(method))
{
m_pfInvoke2 = SaveClassType2<TYPE,
RESULT_TYPE,
PARAM_TYPE,
PARAM_TYPE2>::Invoke;
}
template< typename PARAM_TYPE >
RESULT_TYPE operator()(PARAM_TYPE param)
{
assert(m_pfInvoke);
return (*m_pfInvoke)(m_pObj,m_pFunc,¶m);
}
template< typename PARAM_TYPE, typename PARAM_TYPE2 >
RESULT_TYPE operator()(PARAM_TYPE param1, PARAM_TYPE2 param2)
{
assert(m_pfInvoke);
return (*m_pfInvoke2)(m_pObj,m_pFunc,¶m1,¶m2);
}
private:
void* m_pObj;
pfMethodType m_pFunc;
union
{
pfStaticInvokeFunc m_pfInvoke;
pfStaticInvokeFunc2 m_pfInvoke2;
};
};
Although this class will work it is not very safe. Nothing prevents, for example:
struct F : public A
{
void func(int x)
{
...
}
void func2(int x, int y)
{
...
}
};
typedef delegate_ex<void> delegate_t;
std::list<delegate_t> delegate_list;
delegate_list.push_back(delegate_t(&e,E::func2));
delegate_list.push_back(delegate_t(&f,F::func2));
delegate_list.push_back(delegate_t(&f,F::func));
int y = 0;
for(i = delegate_list.begin();i != delegate_list.end();++i,y++)
{
(*i)(j++,y);
}
Worse things would happen given functions with signatures containing params of different types...
Non-variable params example is type safe as the correct casts are enforced.
Anyway, it's fun to play around with...
Mike
|
|
|
|
|
Mike Junkin wrote:
Anyway, here's a new delegate class that consolidates all the behavior into one class. It uses a templated static method in a nested class to 'save' the behavior required to do the proper cast.
Interesting solution indeed. It is type safe eventhough void pointers are used, much thanks to the template types.
You could optimize the code further by making pfStaticInvokeFunc m_pfInvoke; static. This'll save 4 bytes (depending on platform) per object.
I think I'll study this solution further when I get home.
Mike Junkin wrote:
It is also possible to create a class that'll do multiple parameters or rather variable parameters. But I'm not sure it's worth it because of the type safety issues it puts into play.
Yes, the type system is seriously in danger. I think it's only logical though that operator() 's with different parameter count should be in different interfaces. The number of parameters are indeed a part of the type signature. If you want to maintain type safety of any kind, then multiple interfaces is a must I'm afraid.
Woe to you, Oh Earth and Sea,for the Devil sends the beast with wrath, because he knows the time is short...
Let him who hath understanding reckon the number of the beast for it is a human number, it's number is Six hundred and sixty six
|
|
|
|
|
Jörgen Sigvardsson wrote:
You could optimize the code further by making pfStaticInvokeFunc m_pfInvoke; static. This'll save 4 bytes (depending on platform) per object
I don't think you can make the pointer static. If you do then every delegate, with the same combination of return/arguments, will cast itself as whatever the last created delegate points too. In your example everything would cast itself as a 'D' object.
It's that dynamic pointer that lets the delegates vary their callback class on a dynamic, per instance, basis.
Jörgen Sigvardsson wrote:
If you want to maintain type safety of any kind, then multiple interfaces is a must I'm afraid.
I agree with you. I included the multi-param option only to show it could be done. I don't think it's pratical due to the lack of type safety.
It's possible RTTI could be used to make it safe and useful. In fact RTTI would probably open up other alternatives to both of the classes.
Mike
|
|
|
|
|
Mike Junkin wrote:
I don't think you can make the pointer static. If you do then every delegate, with the same combination of return/arguments, will cast itself as whatever the last created delegate points too. In your example everything would cast itself as a 'D' object.
Doh! You're right. I forgot about the void pointer.
Mike Junkin wrote:
It's possible RTTI could be used to make it safe and useful. In fact RTTI would probably open up other alternatives to both of the classes.
Can you extract RTTI information about method/function signatures? If so, then yes, it would be possible. Allthough, the risk is high that one ends up with the "Java syndrome". All trapped "errors" would have to be guarded by throws - and this doesn't make the implementation exception neutral.
Woe to you, Oh Earth and Sea,for the Devil sends the beast with wrath, because he knows the time is short...
Let him who hath understanding reckon the number of the beast for it is a human number, it's number is Six hundred and sixty six
|
|
|
|
|
It's more than just a pointer. It's an object that contains a pointer to a class and a member function pointer. You can't call the member function of a class without an instance of an object. It's the same as a functor in STL.
Todd Smith
|
|
|
|
|
Russ Freeman wrote:
It's jolly difficult to have a pointer to a member function without 'this'
Actually no it isn't. The address of member functions are stored together with the class information - determined at compile time1. So it is possible to separate 'this' and methods. If you look under the hood of C++, you'll see that 'this' is merely an extra parameter in each member call. VC++ even has a call type for this - __thiscall.
1 With the exception of virtual member functions. These are stored in the virtual pointer table, also known as vtable. I'm not sure how delegates work on virtual methods, but I'm going to find out right now!
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
Russ Freeman wrote:
What!?! Sure it is.
I must have misunderstood you. What I meant from all along was that you can separate the object from the method and later put them together to make a call.
Russ Freeman wrote:
In fact if you'd used the AT&T C++ front end compiler (I did circa 1993) then you could have looked at the intermediary C code and seen the *real* 'this' pointer.
I haven't had the pleasure to try any cfront compiler, that I'm aware of. Gcc, VC nor Borland C++ are not cfront compilers, or are they? I did however have the pleasure of splashing coffee all over a library copy of Stroustrups book "Design and Evolution of C++" - thus I had to buy a copy and replace the damaged one. Fortunately, it was still readable.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
It's not just a pointer to member function. It cloaks a part of the member function type. It also embedds a member function pointer and an object so that the caller will not need to "know about" the object at call time.
Since it hides the class-membership of the method pointer, you can store "similar" delegates in a type safe manner (std::map<>, std::list<>, etc).
So, it is not just a pointer to a member function.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
And for the record: I never claimed it would work with VC6
VC6 is terrible when it comes to template handling. My grand mother would be far more capable than the VC6 compiler.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
IIRC, this is legal C++:
void func() { }
void func2() {
return func();
}
Allthough, most compilers may not support it. But I'm sure as time goes on, compilers will be more compliant
to the C++ standards. I think VC7 is proof of that, and it's only getting better.
If the compiler you use does not support "returning voids", then you could use int as a dummy return type. It's a small sacrifice for making it work.
As I don't have VC6 installed, I cammot test this delegate pattern on that compiler. If the void is the only thing that's broken for
that compiler, then all is "fine and dandy" I suppose. If there is anything else broken, I'd love to hear about it and would be more than delighted if you gave me a patch or hints on making it work.
I plan on installing Cygwin later too, to see if the code works with gcc. I suspect though it will not work with gcc < 2.95. Judging by www.boost.org[^], gcc seems to be the most compliant compiler in production these days, so I'm confident it'll work there too.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
Jörgen Sigvardsson wrote:
IIRC, this is legal C++:
void func() { } void func2() { return func();}
Yep, full legal and standard behaviour
BTW , nice article Jörgen
Jörgen Sigvardsson wrote:
gcc seems to be the most compliant compiler in production these days
I think it isn't , the $50 Comeau compiler[^] is the most standard compiler , albeit it need c compilers backends to process the C generated code.
Cheers,Joao Vaz
And if your dream is to care for your family, to put food on the table, to provide them with an education and a good home, then maybe suffering through an endless, pointless, boring job will seem to have purpose. And you will realize how even a rock can change the world, simply by remaining obstinately stationary.-Shog9
|
|
|
|
|
Joao Vaz wrote:
BTW , nice article Jörgen
Thank you for your kind words!
Joao Vaz wrote:
I think it isn't , the $50 Comeau compiler[^] is the most standard compiler
Ah yes, I remember now. Comeau was the first compilers to ship with extern templates if my memory serves me right.
Joao Vaz wrote:
albeit it need c compilers backends to process the C generated code.
Is it a cfront-compiler based on AT&T code or is it a homebrew compiler? I've always wanted to have a look at the output of a cfront compiler to gain more knowledge about C++.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
Jörgen Sigvardsson wrote:
Thank you for your kind words!
Nope. You have a good knowledge of C++ for your age (vastly superior to mine), and since the article uses templates , illustrated a good technique of emulation of delegates ,and the explanation of the technique is good, it was only fair that I gave it a 5
Jörgen Sigvardsson wrote:
Is it a cfront-compiler based on AT&T code or is it a homebrew compiler?
The compiler is based on EDG[^], a quality front end, that supports cfront 2.1 e 3.0 .
Cheers,Joao Vaz
And if your dream is to care for your family, to put food on the table, to provide them with an education and a good home, then maybe suffering through an endless, pointless, boring job will seem to have purpose. And you will realize how even a rock can change the world, simply by remaining obstinately stationary.-Shog9
|
|
|
|
|
A little dated , but a good article (a link provided by Joaquin)about the internals of VC++ , IMHO . a interesting read http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarvc/html/jangrayhood.asp[^]
Cheers,Joao Vaz
And if your dream is to care for your family, to put food on the table, to provide them with an education and a good home, then maybe suffering through an endless, pointless, boring job will seem to have purpose. And you will realize how even a rock can change the world, simply by remaining obstinately stationary.-Shog9
|
|
|
|
|
Unfortunately, the link no longer works. I'll try to search for it using keywords.
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
Never mind my earlier post. I tried the link again, and it worked!
The Romulans are... beyond arrogant.
202.The justification of profit is profit.
|
|
|
|
|
Nice article, thanks for the link.
If you like that type of thing there's a good book: Inside the C++ Object Model by Stanley B. Lippman.
Mike
|
|
|
|
|
Excellent bed-time reading!
Woe to you, Oh Earth and Sea,for the Devil sends the beast with wrath, because he knows the time is short...
Let him who hath understanding reckon the number of the beast for it is a human number, it's number is Six hundred and sixty six
|
|
|
|
|
Jörgen Sigvardsson wrote:
Excellent bed-time reading!
I'm so envy of you
Cheers,Joao Vaz
And if your dream is to care for your family, to put food on the table, to provide them with an education and a good home, then maybe suffering through an endless, pointless, boring job will seem to have purpose. And you will realize how even a rock can change the world, simply by remaining obstinately stationary.-Shog9
|
|
|
|
|
Mike Junkin wrote:
there's a good book: Inside the C++ Object Model
This book is on my wishlist ,I only expecting better days to buy it
Cheers,Joao Vaz
And if your dream is to care for your family, to put food on the table, to provide them with an education and a good home, then maybe suffering through an endless, pointless, boring job will seem to have purpose. And you will realize how even a rock can change the world, simply by remaining obstinately stationary.-Shog9
|
|
|
|
|
The .NET Delegates support any number of parameters, can you find a way to let your code accept any number of parameters.
ZhangZQ71
email:zhangzq71@hotmail.com
|
|
|
|
|