5年后,有什么比“最快的可能C ++代理”更好的? [英] 5 years later, is there something better than the "Fastest Possible C++ Delegates"?

查看:240
本文介绍了5年后,有什么比“最快的可能C ++代理”更好的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道C ++代表的主题已经完成死亡,并且两者都 http://www.codeproject。 com http://stackoverflow.com 深深地涵盖了这个问题。



一般来说, Don Clugston最快的代表是许多人的首选。还有一些其他流行的。



但是,我注意到,大多数的文章都是旧的(2005年左右),许多设计选择似乎已经考虑到旧的编译器,如VC7。



我需要一个非常快速的委托实现音频应用程序。



我仍然需要它是便携式的(Windows,Mac,Linux),但我只使用现代编译器(VC9,VS2008 SP1和GCC 4.5.x)。



我的主要标准是:





  • 它必须与新版本的编译器向前兼容。对于Don的实现,我有一些疑问,因为他明确表示不符合标准。

  • 可选地,KISS语法和易于使用是很好的



此外,我真的不需要异国情调的特点。我只需要好的老指针到方法的东西。不需要支持静态方法,自由函数或类似的东西。



截至今天,推荐的方法是什么?仍然使用 Don的版本
或者有关于另一个选项的社群共识吗?



我真的不想使用Boost.signal / signal2,因为它在性能方面是不能接受的。



此外,我已经看到一些较新的库,而googling,例如 cpp-events ,但我找不到任何来自用户的反馈,包括SO。

解决方案

更新: 文章



好的,指向方法的指针的问题是,它们是不是所有的尺寸相同。因此,不是直接存储指向方法的指针,我们需要标准化它们,使它们具有恒定的大小。这是Don Clugston试图在他的代码项目文章中实现的。他使用最流行的编译器的密切知识这样做。我断言它可以在正常C ++中做,而不需要这样的知识。



考虑下面的代码:

  void DoSomething(int)
{
}

void InvokeCallback(void(* callback)(int))
{
callback(42);
}

int main()
{
InvokeCallback(& DoSomething);
return 0;
}

这是一种使用简单的旧函数指针实现回调的方法。但是,这不适用于对象中的方法。让我们解决这个问题:

  class Foo 
{
public:
void DoSomething {}

static void DoSomethingWrapper(void * obj,int param)
{
static_cast< Foo *>(obj) - > DoSomething
}
};

void InvokeCallback(void * instance,void(* callback)(void *,int))
{
callback(instance,42);
}

int main()
{
Foo f;
InvokeCallback(static_cast< void *>(& f),& Foo :: DoSomethingWrapper);
return 0;
}

现在,我们有一个回调系统,功能。然而,这是笨拙和容易出错的。然而,有一种模式 - 使用包装函数将静态函数调用转发到正确实例上的方法调用。我们可以使用模板来帮助这一点 - 让我们尝试推广包装函数:

  template< typename R,class T,typename A1 ,R(T :: * Func)(A1) 
R Wrapper(void * o,A1 a1)
{
return(static_cast< T *(o) - > * Func)

}

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),
& amp; Wrapper< void,Foo,int,& Foo :: DoSomething>
return 0;
}

这还是非常笨拙,但至少现在我们不必每次写出一个包装器函数(至少对于1个参数情况)。另一个我们可以概括的事实是,我们总是传递一个指针 void * 。不是把它作为两个不同的值,为什么不把它们放在一起?虽然我们这样做,为什么不推广它呢?嘿,让我们引入一个运算符()(),所以我们可以像一个函数一样调用它。

  template< typename R,typename A1> 
class Callback
{
public:
typedef R(* FuncType)(void *,A1);

回调函数(void * o,FuncType f):obj(o),func(f){}
R运算符()(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)

}

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;
}

我们正在取得进展!但现在我们的问题是事实,语法是绝对可怕的。语法看起来冗余;不能编译器从指针到方法本身的类型?可惜没有,但我们可以帮助它。记住,编译器可以通过在函数调用中的模板参数推导来推导类型。

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

函数,我们知道 R T A1 是。那么如果我们可以构造一个可以保存这些类型并从函数中返回的结构体呢?

  template< typename R ,类别T,类型名称A1> 
struct DeduceMemCallbackTag
{
};

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

由于 DeduceMemCallbackTag 知道类型,为什么不把我们的wrapper函数作为一个静态函数呢?因为包装器函数在其中,为什么不把代码构造它的 Callback 对象?

  template< typename R,typename A1> 
class Callback
{
public:
typedef R(* FuncType)(void *,A1);

回调函数(void * o,FuncType f):obj(o),func(f){}
R运算符()(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)
}

template< R(T :: * Func)(A1)>
inline static Callback< R,A1> Bind(T * o)
{
return Callback< R,A1>(o,& DeduceMemCallbackTag :: Wrapper< Func>);
}
};

模板< typename R,class T,typename A1>
DeduceMemCallbackTag< R,T,A1> DeduceMemCallback(R(T :: *)(A1))
{
return DeduceMemCallbackTag< R,T,A1&
}

C ++标准允许我们调用实例上的静态函数/ p>

  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;
}

仍然是一个冗长的表达式,但我们可以把它放入一个简单的宏(!):

  template< typename R,typename A1> 
class Callback
{
public:
typedef R(* FuncType)(void *,A1);

回调函数(void * o,FuncType f):obj(o),func(f){}
R运算符()(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)
}

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;
}

我们可以增强回调 object通过添加一个安全的bool。它也是一个好主意,禁用等于运算符,因为它不可能比较两个回调对象。更好的是,使用部分专门化来允许首选语法。这给了我们:

 模板< typename FuncSignature> 
class Callback;

template< typename R,typename A1>
class Callback< R(A1)>
{
public:
typedef R(* FuncType)(void *,A1);

回调():obj(0),func(0){}
回调(void * o,FuncType f):obj
$ b 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> //未定义目的
void operator ==(const Callback< R(A1)& const,Callback< R(A1)&
template< typename R,typename A1>
void operator!=(const Callback< R(A1)& const; Call 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)
}

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:
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;
}



我已经在Visual C ++编译器上测试了这个(版本15.00.30729.01, VS 2008附带的),你需要一个相当近的编译器来使用代码。通过检查反汇编,编译器能够优化掉包装器函数和 DeduceMemCallback 调用,减少到简单的指针赋值。



这对于回调的两端都很简单,只使用(我相信是)标准C ++。我上面显示的代码适用于具有1个参数的成员函数,但可以推广到更多的参数。



请注意, Callback 对象不需要堆分配 - 由于这种标准化程序,它们具有恒定的尺寸。因为这个,可能有一个 Callback 对象是较大的类的成员,因为它有一个默认的构造函数。它也是可分配的(编译器生成的复制分配函数就足够了)。它也是类型安全,感谢模板。


I know that the topic of "C++ delegates" has been done to death, and both http://www.codeproject.com and http://stackoverflow.com deeply cover the question.

Generally, it seems that Don Clugston's fastest possible delegate is the first choice for many people. There are a few other popular ones.

However, I noticed that most of those articles are old (around 2005) and many design choices seem to have been made taking in account old compilers like VC7.

I'm in need of 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 are:

  • it must be fast!
  • it must be forward-compatible with newer versions of the compilers. I have some doubts about that with Don's implementation because he explicitly states it's not standard-compliant.
  • optionally, a KISS-syntax and ease-of-use is nice to have
  • multicast would be nice, although I'm convinced it's really easy to build it around any delegate library

Furthermore, I don't really need exotic features. I just need the good old pointer-to-method thing. No need to support static methods, free functions or things like that.

As of today, what is the recommended approach? Still use Don's version? Or is there a "community consensus" about another option?

I really don't want to use Boost.signal/signal2 because it's not acceptable in terms of performance. A dependency on QT is not acceptable as well.

Furthermore, I've seen some newer libraries while googling, like for example cpp-events but I couldn't find any feedback from users, including on SO.

解决方案

Update: An article with the complete source code and a more detailed discussion has been posted on The Code Project.

Well, the problem with pointers to methods is that they're not all the same size. So instead of storing pointers to methods directly, we need to "standardize" them so that they are of a constant size. This is what Don Clugston attempts to achieve in his Code Project article. He does so using intimate knowledge of the most popular compilers. I assert that it's possible to do it 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 a plain old function pointer. However, this doesn't work for methods in objects. Let's fix this:

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;
}

Now, we have a system of callbacks that can work for both free and member functions. This, however, is clumsy and error-prone. However, there is a pattern - the use of a wrapper function to "forward" the static function call to a method call on the proper instance. We can use templates to help with this - let's 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 clumsy, but at least now we don't have to write out a wrapper function every single time (at least for the 1 argument case). Another thing we can generalize is the fact that we're always passing a pointer to void*. Instead of passing it as two different values, why not put them together? And while we're doing that, why not generalize it as well? Hey, let's throw in an operator()() so we can call it like 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're making progress! But now our problem is the fact that the syntax is absolutely horrible. The syntax appears redundant; can't the compiler figure out the types from the pointer to method itself? Unfortunately no, but we can help it along. Remember that a compiler can deduce types via template argument deduction in a function call. So why don't we start with that?

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

Inside the function, we know what R, T and A1 is. So what if we can construct a struct that can "hold" these types and return them from the 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 the types, why not put our wrapper function as a static function in it? And since the wrapper function is in it, why not put the code to construct 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 us 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;
}

Still, it's a lengthy expression, but we can put that into 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 enhance the Callback object by adding a safe bool. It's also a good idea to disable the equality operators since it's not possible to compare two Callback objects. Even better, is to use partial specialization to allow for a "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 have tested this on the Visual C++ compiler (version 15.00.30729.01, the one that comes with VS 2008), and you do need a rather recent compiler to use the code. By inspection of the disassembly, the compiler was able to optimize away the wrapper function and the DeduceMemCallback call, reducing down to simple pointer assignments.

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

Note that the Callback object requires no heap allocation - they are of a constant size thanks to this "standardization" procedure. Because of this, it's possible to have a Callback object be a member of larger class, since it has a default constructor. It is also assignable (the compiler generated copy assignment functions are sufficient). It is also typesafe, thanks to the templates.

这篇关于5年后,有什么比“最快的可能C ++代理”更好的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆