5 years later, is there anything better than “the fastest possible delegates in C ++”?

I know that the theme of "C ++ delegates" was brought to death, and http://www.codeproject.com and /qaru.site / ... deeply cover the issue.

It usually seems that Don Cloughston - the fastest delegate possible - is the first choice for many people. There are several more popular ones.

However, I noticed that most of these articles are old (around 2005), and many design options seem to have been made with older compilers like VC7 in mind.

I need a very fast delegate implementation for an audio application.

I still need it to be portable (Windows, Mac, Linux), but I only use modern compilers (VC9, the one in VS2008 SP1 and GCC 4.5.x).

My main criteria:

  • he must be fast!
  • It must be compatible with newer versions of compilers. I have some doubts about the implementation of Don, because it explicitly states that it does not meet the standard.
  • Optional, KISS syntax and ease of use are nice to have
  • multicasting will be enjoyable, although I am convinced that it is very easy to create it in any delegate library.

Also, I really don't need exotic features. I just need a good old thing with a pointer to a method. There is no need to support static methods, free functions, or anything like that.

Today, what is the recommended approach? Still using the Don version ? Or is there a "community consensus" about another option?

I really don't want to use Boost.signal / signal2 because it is not acceptable in terms of performance. Dependence on QT is also unacceptable.

In addition, I saw several new libraries while searching googling, such as cpp-events , but I could not find user reviews, including on SO.

+71
c ++ performance delegates
Nov 28 2018-10-18T00-11-28
source share
2 answers

Update: A full source article and a more detailed discussion are published in the Code project.

Well, the problem with method pointers is that they are not all the same size. Therefore, instead of directly storing pointers to methods, we need to "standardize" them so that they have a constant size. This is what Don Clugston is trying to do in his Draft Codex article. He does this using the intimate knowledge of the most popular compilers. I argue that this can be done in “normal” C ++ without requiring such knowledge.

Consider the following code:

void DoSomething(int) { } void InvokeCallback(void (*callback)(int)) { callback(42); } int main() { InvokeCallback(&DoSomething); return 0; } 

This is one way to implement a callback using the plain old function pointer. However, this does not work for methods in objects. Let fix it:

 class Foo { public: void DoSomething(int) {} static void DoSomethingWrapper(void* obj, int param) { static_cast<Foo*>(obj)->DoSomething(param); } }; void InvokeCallback(void* instance, void (*callback)(void*, int)) { callback(instance, 42); } int main() { Foo f; InvokeCallback(static_cast<void*>(&f), &Foo::DoSomethingWrapper); return 0; } 

We now have a callback system that can work for both free and member functions. This, however, is inconvenient and error prone. However, there is a pattern - using a wrapper function to "forward" a call to a static function to a method call in the corresponding instance. We can use templates to help with this - try generalizing the wrapper function:

 template<typename R, class T, typename A1, R (T::*Func)(A1)> R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } class Foo { public: void DoSomething(int) {} }; void InvokeCallback(void* instance, void (*callback)(void*, int)) { callback(instance, 42); } int main() { Foo f; InvokeCallback(static_cast<void*>(&f), &Wrapper<void, Foo, int, &Foo::DoSomething> ); return 0; } 

This is still extremely awkward, but at least for now we don’t need to write a wrapper function every time (at least for argument 1 of the argument). Another thing that we can generalize is that we always pass a pointer to void* . Instead of passing it as two different meanings, why not put them together? And while we are doing this, why not generalize it? Hey, let me insert operator()() so we can name it as a function!

 template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1, R (T::*Func)(A1)> R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; Callback<void, int> cb(static_cast<void*>(&f), &Wrapper<void, Foo, int, &Foo::DoSomething>); InvokeCallback(cb); return 0; } 

We are making progress! But now our problem is that the syntax is absolutely terrible. The syntax seems redundant; can the compiler determine the types from the pointer to the method itself? Unfortunately not, but we can help him. Remember that the compiler can infer types through the output of a template argument in a function call. So why don't we start with this?

 template<typename R, class T, typename A1> void DeduceMemCallback(R (T::*)(A1)) {} 

Inside the function, we know what R , T and A1 . So what if we can build a structure that can “hold” these types and return them from a function?

 template<typename R, class T, typename A1> struct DeduceMemCallbackTag { }; template<typename R, class T, typename A1> DeduceMemCallbackTag2<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); } 

And since DeduceMemCallbackTag knows about types, why not put our wrapper function as a static function in it? And since it has a wrapper function, why not put the code to create our Callback object in it?

 template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1> struct DeduceMemCallbackTag { template<R (T::*Func)(A1)> static R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } template<R (T::*Func)(A1)> inline static Callback<R, A1> Bind(T* o) { return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>); } }; template<typename R, class T, typename A1> DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); } 

The C ++ standard allows you to call static functions on instances (!):

 class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; InvokeCallback( DeduceMemCallback(&Foo::DoSomething) .Bind<&Foo::DoSomething>(&f) ); return 0; } 

However, this is a long expression, but we can put this in a simple macro (!):

 template<typename R, typename A1> class Callback { public: typedef R (*FuncType)(void*, A1); Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } private: void* obj; FuncType func; }; template<typename R, class T, typename A1> struct DeduceMemCallbackTag { template<R (T::*Func)(A1)> static R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } template<R (T::*Func)(A1)> inline static Callback<R, A1> Bind(T* o) { return Callback<R, A1>(o, &DeduceMemCallbackTag::Wrapper<Func>); } }; template<typename R, class T, typename A1> DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); } #define BIND_MEM_CB(memFuncPtr, instancePtr) \ (DeduceMemCallback(memFuncPtr).Bind<(memFuncPtr)>(instancePtr)) class Foo { public: void DoSomething(int) {} }; void InvokeCallback(Callback<void, int> callback) { callback(42); } int main() { Foo f; InvokeCallback(BIND_MEM_CB(&Foo::DoSomething, &f)); return 0; } 

We can improve the Callback object by adding a secure bool. It’s also nice to turn off equality operators, since you cannot compare two Callback objects. Even better, use partial specialization to allow "preferred syntax". This gives us:

 template<typename FuncSignature> class Callback; template<typename R, typename A1> class Callback<R (A1)> { public: typedef R (*FuncType)(void*, A1); Callback() : obj(0), func(0) {} Callback(void* o, FuncType f) : obj(o), func(f) {} R operator()(A1 a1) const { return (*func)(obj, a1); } typedef void* Callback::*SafeBoolType; operator SafeBoolType() const { return func != 0? &Callback::obj : 0; } bool operator!() const { return func == 0; } private: void* obj; FuncType func; }; template<typename R, typename A1> // Undefined on purpose void operator==(const Callback<R (A1)>&, const Callback<R (A1)>&); template<typename R, typename A1> void operator!=(const Callback<R (A1)>&, const Callback<R (A1)>&); template<typename R, class T, typename A1> struct DeduceMemCallbackTag { template<R (T::*Func)(A1)> static R Wrapper(void* o, A1 a1) { return (static_cast<T*>(o)->*Func)(a1); } template<R (T::*Func)(A1)> inline static Callback<R (A1)> Bind(T* o) { return Callback<R (A1)>(o, &DeduceMemCallbackTag::Wrapper<Func>); } }; template<typename R, class T, typename A1> DeduceMemCallbackTag<R, T, A1> DeduceMemCallback(R (T::*)(A1)) { return DeduceMemCallbackTag<R, T, A1>(); } #define BIND_MEM_CB(memFuncPtr, instancePtr) \ (DeduceMemCallback(memFuncPtr).Bind<(memFuncPtr)>(instancePtr)) 

Usage example:

 class Foo { public: float DoSomething(int n) { return n / 100.0f; } }; float InvokeCallback(int n, Callback<float (int)> callback) { if(callback) { return callback(n); } return 0.0f; } int main() { Foo f; float result = InvokeCallback(97, BIND_MEM_CB(&Foo::DoSomething, &f)); // result == 0.97 return 0; } 

I tested this on the Visual C ++ compiler (version 15.00.30729.01, the one that comes with VS 2008), and you need a fairly recent compiler to use the code. When checking for disassembly, the compiler was able to optimize the wrapper function and call DeduceMemCallback , reducing it to simple pointer assignments.

It is easy to use for both sides of the callback and uses only (what I consider) standard C ++. The code shown above works for member functions with 1 argument, but can be generalized to more arguments. It can also be generalized by allowing support for static functions.

Note that the Callback object does not require heap allocation - they are of constant size due to this “standardization” procedure. Because of this, it is possible that the Callback object will be a member of a larger class, since it has a default constructor. It can also be assigned (copy generation functions created by the compiler are sufficient). This is also typical, thanks to the patterns.

+118
Nov 29 '10 at 3:05
source share

I wanted to track @Insilico's answer with a bit of my own stuff.

Before I stumbled upon this answer, I also tried to find quick callbacks that did not incur overhead and were uniquely comparable / identified only by function signature. What I created - with some serious help from the Klingons that happened on the barbecue - works for all types of functions (except Lambdas, if you do not store Lambda, but do not try, because it is really difficult and difficult to do, and can lead to that the robot will prove to you how difficult it is and makes you have shit for him ). Thanks to @sehe, @nixeagle, @StackedCrooked, @CatPlusPlus, @Xeo, @DeadMG and, of course, @Insilico for their help in creating the event system. Feel free to use as you wish.

In any case, the example is based on ideone, but the source code is also here for your use (because, since Liveworkspace has gone down, I do not believe them in shadow compilation of services. Who knows when the ideon will go down ?!), I hope this is useful for those who are not busy with Lambda / Function, contrasting the world with goals:

IMPORTANT NOTE: Currently (11/28/2012, 9:35 PM) This variation version will not work with Microsoft VC ++ 2012 November CTP (Milan). If you want to use it with this, you will have to get rid of all the variational material and explicitly list the number of arguments (and, possibly, a template-specialize type of 1-argument for Event for void ) to make it work. This is a pain, and before I got tired (and decided that the passage of more than 4 arguments was a little stretched) I managed to write only 4 arguments.

Source Example

Source:

 #include <iostream> #include <vector> #include <utility> #include <algorithm> template<typename TFuncSignature> class Callback; template<typename R, typename... Args> class Callback<R(Args...)> { public: typedef R(*TFunc)(void*, Args...); Callback() : obj(0), func(0) {} Callback(void* o, TFunc f) : obj(o), func(f) {} R operator()(Args... a) const { return (*func)(obj, std::forward<Args>(a)...); } typedef void* Callback::*SafeBoolType; operator SafeBoolType() const { return func? &Callback::obj : 0; } bool operator!() const { return func == 0; } bool operator== (const Callback<R (Args...)>& right) const { return obj == right.obj && func == right.func; } bool operator!= (const Callback<R (Args...)>& right) const { return obj != right.obj || func != right.func; } private: void* obj; TFunc func; }; namespace detail { template<typename R, class T, typename... Args> struct DeduceConstMemCallback { template<R(T::*Func)(Args...) const> inline static Callback<R(Args...)> Bind(T* o) { struct _ { static R wrapper(void* o, Args... a) { return (static_cast<T*>(o)->*Func)(std::forward<Args>(a)...); } }; return Callback<R(Args...)>(o, (R(*)(void*, Args...)) _::wrapper); } }; template<typename R, class T, typename... Args> struct DeduceMemCallback { template<R(T::*Func)(Args...)> inline static Callback<R(Args...)> Bind(T* o) { struct _ { static R wrapper(void* o, Args... a) { return (static_cast<T*>(o)->*Func)(std::forward<Args>(a)...); } }; return Callback<R(Args...)>(o, (R(*)(void*, Args...)) _::wrapper); } }; template<typename R, typename... Args> struct DeduceStaticCallback { template<R(*Func)(Args...)> inline static Callback<R(Args...)> Bind() { struct _ { static R wrapper(void*, Args... a) { return (*Func)(std::forward<Args>(a)...); } }; return Callback<R(Args...)>(0, (R(*)(void*, Args...)) _::wrapper); } }; } template<typename R, class T, typename... Args> detail::DeduceConstMemCallback<R, T, Args...> DeduceCallback(R(T::*)(Args...) const) { return detail::DeduceConstMemCallback<R, T, Args...>(); } template<typename R, class T, typename... Args> detail::DeduceMemCallback<R, T, Args...> DeduceCallback(R(T::*)(Args...)) { return detail::DeduceMemCallback<R, T, Args...>(); } template<typename R, typename... Args> detail::DeduceStaticCallback<R, Args...> DeduceCallback(R(*)(Args...)) { return detail::DeduceStaticCallback<R, Args...>(); } template <typename... T1> class Event { public: typedef void(*TSignature)(T1...); typedef Callback<void(T1...)> TCallback; typedef std::vector<TCallback> InvocationTable; protected: InvocationTable invocations; public: const static int ExpectedFunctorCount = 2; Event() : invocations() { invocations.reserve(ExpectedFunctorCount); } template <void (* TFunc)(T1...)> void Add() { TCallback c = DeduceCallback(TFunc).template Bind<TFunc>(); invocations.push_back(c); } template <typename T, void (T::* TFunc)(T1...)> void Add(T& object) { Add<T, TFunc>(&object); } template <typename T, void (T::* TFunc)(T1...)> void Add(T* object) { TCallback c = DeduceCallback(TFunc).template Bind<TFunc>(object); invocations.push_back(c); } template <typename T, void (T::* TFunc)(T1...) const> void Add(T& object) { Add<T, TFunc>(&object); } template <typename T, void (T::* TFunc)(T1...) const> void Add(T* object) { TCallback c = DeduceCallback(TFunc).template Bind<TFunc>(object); invocations.push_back(c); } void Invoke(T1... t1) { for(size_t i = 0; i < invocations.size() ; ++i) invocations[i](std::forward<T1>(t1)...); } void operator()(T1... t1) { Invoke(std::forward<T1>(t1)...); } size_t InvocationCount() { return invocations.size(); } template <void (* TFunc)(T1...)> bool Remove () { return Remove (DeduceCallback(TFunc).template Bind<TFunc>()); } template <typename T, void (T::* TFunc)(T1...)> bool Remove (T& object) { return Remove <T, TFunc>(&object); } template <typename T, void (T::* TFunc)(T1...)> bool Remove (T* object) { return Remove (DeduceCallback(TFunc).template Bind<TFunc>(object)); } template <typename T, void (T::* TFunc)(T1...) const> bool Remove (T& object) { return Remove <T, TFunc>(&object); } template <typename T, void (T::* TFunc)(T1...) const> bool Remove (T* object) { return Remove (DeduceCallback(TFunc).template Bind<TFunc>(object)); } protected: bool Remove( TCallback const& target ) { auto it = std::find(invocations.begin(), invocations.end(), target); if (it == invocations.end()) return false; invocations.erase(it); return true; } }; 
+10
Nov 28 '12 at 21:14
source share



All Articles