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

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

问题描述

我知道C ++代理的主题已经完成了死亡, http://www.codeproject。 com http://stackoverflow.com 深入讨论此问题。

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.

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

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

然而,我注意到大多数这些文章都是旧的(大约在2005年左右),许多设计选择似乎已经被考虑在内像VC7这样的旧编译器。

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.

我仍然需要它可移植(Windows,Mac,Linux),但我只使用现代编译器(VC9,VS2008 SP1和GCC 4.5.x中的编译器)。

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).

我的主要标准是:


  • 一定要快!

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

  • 可选地,KISS语法和易用性很好,具有

  • 多播会很好,虽然我相信它很容易在任何委托库附近构建。

  • 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?

我真的不想使用Boost.signal / signal2,因为在性能方面是不能接受的。对QT的依赖也是不可接受的。

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.

此外,我在Google搜索时看到一些较新的库,例如 cpp-events ,但我找不到任何用户反馈,包括SO。

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.

推荐答案

更新: 有关完整源代码和更详细讨论的文章已经发布在代码项目上。

那么指向方法的问题是他们的尺寸不一样。因此,我们不需要直接存储指向方法的指针,而是需要对它们进行标准化,使其具有一定的大小。唐·坎普斯顿(Don Clugston)在他的代码项目(Code Project)文章中试图实现这一点。他使用最受欢迎的编译器的亲密知识。我断言可以在普通C ++中执行此操作,而不需要这样的知识。

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.

考虑以下代码:

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

这仍然非常笨拙,但至少现在我们不必每一次写出一个包装函数(至少对于1个参数)。我们可以概括的另一件事是,我们总是传递一个指向 void * 的指针。而不是把它作为两个不同的价值观,为什么不把它们放在一起?在我们这样做的时候,为什么不把它概括一下呢?嘿,让我们扔一个 operator()(),所以我们可以像函数一样调用它!

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)) {}

里面功能,我们知道什么 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>();
}

DeduceMemCallbackTag 知道类型,为什么不把我们的包装函数当作静态函数呢?而且由于包装函数在其中,为什么不把代码构造在我们的回调对象中?

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

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

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

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

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))

使用示例:

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 调用,减少到简单的指针分配。

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.

对回调的双方使用简单,只使用(我认为是)标准的C ++。上面显示的代码适用于具有1个参数的成员函数,但可以将其推广到更多参数。

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.

请注意,回调对象不需要堆分配 - 由于这种标准化程序,它们的尺寸不变。因此,可以使用 Callback 对象作为较大类的成员,因为它具有默认构造函数。它也是可分配的(编译器生成的副本分配函数就足够了)。它也是类型安全的,谢谢模板。

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天全站免登陆