5年后,还有什么比“最快的C ++代表”更好的了?


72

我知道“ C ++委托”这一主题已经死了,http://www.codeproject.comhttp://stackoverflow.com都深深地覆盖了这个问题。

通常,似乎唐·克拉格斯顿(Don Clugston)最快的代表是许多人的首选。还有其他一些流行的。

但是,我注意到其中大多数文章都是较旧的(大约在2005年),并且许多设计选择似乎都是考虑到了诸如VC7之类的旧编译器。

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

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

我的主要标准是:

  • 它一定要快!
  • 它必须与较新版本的编译器向前兼容。对于Don的实现,我对此表示怀疑,因为他明确声明它不符合标准。
  • 可选地,KISS语法和易用性很高兴
  • 多播会很好,尽管我坚信围绕任何委托库构建它确实很容易

此外,我真的不需要异国情调的功能。我只需要很好的旧方法指针即可。无需支持静态方法,自由函数或类似的东西。

到目前为止,推荐的方法是什么?仍使用Don版本吗?还是有关于另一种选择的“社区共识”?

我真的不想使用Boost.signal / signal2,因为就性能而言这是不可接受的。对QT的依赖也不可接受。

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


4
而且,还有一年多以后:C ++ 11是否针对此问题添加了一些内容?似乎没有,但我可能已经错过了一些显而易见的事情:)
Dinaiz 2012年

Answers:


120

更新: The Code Project上发布了具有完整源代码和更详细讨论的文章。

好吧,方法指针的问题在于它们的大小不尽相同。因此,与其直接存储指向方法的指针,不如将它们“标准化”,以使它们的大小恒定。这就是Don Clugston在其“代码项目”文章中试图实现的目标。他使用最流行的编译器的深入知识来这样做。我断言可以在“普通” C ++中完成此操作而无需掌握此类知识。

考虑以下代码:

void DoSomething(int)
{
}

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

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

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

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

现在,我们有了一个回调系统,该回调系统可以同时用于免费函数和成员函数。然而,这是笨拙且容易出错的。但是,有一种模式-使用包装函数将静态函数调用“转发”到适当实例上的方法调用。我们可以使用模板来解决这个问题-让我们尝试概括包装函数:

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()()以便我们可以像函数一样调用它!

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

我们正在进步!但是现在我们的问题是语法绝对可怕。语法显得多余;编译器无法从方法本身的指针中找出类型吗?不幸的是,没有,但是我们可以提供帮助。请记住,编译器可以通过函数调用中的模板参数推导来推断类型。那为什么不从那开始呢?

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

在函数内部,我们知道什么RTA1是。那么,如果我们可以构造一个可以“保留”这些类型并从函数中返回它们的结构,该怎么办?

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知道类型,为什么不将包装函数作为静态函数放在其中呢?而且由于包装函数在其中,为什么不在其中放入用于构造Callback对象的代码呢?

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 ++标准允许我们在实例(!)上调用静态函数:

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

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

我们可以Callback通过添加安全布尔来增强对象。禁用相等运算符也是一个好主意,因为不可能比较两个Callback对象。更好的是使用部分特殊化以允许“首选语法”。这给我们:

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调用,从而简化为简单的指针分配。

在回调的两边使用都很简单,并且仅使用(我相信是)标准C ++。我上面显示的代码适用于带有1个参数的成员函数,但可以推广到更多参数。也可以通过允许支持静态功能来进一步推广。

请注意,该Callback对象不需要分配堆-由于采用了“标准化”过程,它们的大小是恒定的。因此,由于Callback对象具有默认构造函数,因此可以使其成为较大类的成员。它也是可分配的(编译器生成的副本分配功能已足够)。由于有了模板,它也是类型安全的。


1
几年前,我尝试这样做,并且在标准允许的情况下,使用指向成员函数的指针作为模板参数,在Visual C ++(我认为它是2005 SP1版本)中导致内部编译器错误。因此,确实需要使用最新的编译器版本进行警告,这是一个非常好的方法。
Ben Voigt

2
@Dinaiz:0-5参数版本确实是几乎同样的事情上面,但是事情好像DeduceMemCallbackTag5跟参数一样A1A2A3等局部模板特殊化业务使其透明。它确实会分解成数百行代码,这使其本身很适合用作库。我现在有点忙,但是稍后我将写一篇带有完整源代码的文章。这个堆栈溢出答案足够长了。
计算机上的

2
@j_random_hacker:好问题。该Wrapper<>()函数在编译时需要方法的指针,因此用户代码必须通过非类型模板参数(即的唯一非类型模板参数)提供方法指针Bind<>()。的唯一目的DeduceMemCallback()是推导组成方法指针的类型,并返回一个“知道”流程中推导类型的虚拟对象。这允许Bind<>()接受方法指针作为非类型参数,而不必显式接受构成方法指针的类型。
在硅片上

3
@John:实际上,有一种方法可以利用一个定义规则使这些代表具有可比性。在撰写本文时,我并不知道ODR适用于获取功能模板的地址(请注意,链接的问题要比我的提出晚了)。我已经做了一些测试,它似乎可以工作。您需要做的只是比较函数指针和对象指针。有空时,我可能会在将来更新此答案和文章。
在计算机上2012年

1
这是我在S / O上见过的最史诗般的答案。太棒了!
Reuben Scratton

10

我想用我自己的东西来关注@Insilico的答案。

在我迷茫地回答这个问题之前,我试图找出快速回调方法,该方法不产生开销,并且唯一可比较/仅由函数签名标识。我最终创建的内容-在Klingons的帮助下进行了认真的烧烤-适用于所有函数类型(Lambda除外,除非您存储Lambda,但不要尝试它,因为它确实很难做,并且可能会导致机器人向您证明它有多难,并让您为此吃掉东西)。感谢@ sehe,@ nixeagle,@ StackedCrooked,@ CatPlusPlus,@ Xeo,@ DeadMG,当然还有@Insilico,以帮助创建事件系统。随意使用。

无论如何,有关ideone的例子很多,但是源代码也在这里供您使用(因为Liveworkspace出现故障,所以我不相信它们的幕后编译服务。谁知道ideone何时会下降?!)。我希望这对不忙Lambda / Function-objecting世界的人有用:

重要说明:截至目前(2012年11月28日,晚上9:35),此可变版本不能与Microsoft VC ++ 2012年11月CTP(米兰)一起使用。如果你想与使用它,你将不得不摆脱所有的可变参数的东西,并明确列举的参数(以及可能的模板专门的1-参数类型数Eventvoid),使其工作。这很痛苦,在累之前,我只能设法为4个参数写出来(并决定传递4个以上的参数有点麻烦)。

源示例

资源:

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

您是否愿意与较早的编译器共享非可变的,高达4参数的版本?谢谢!
戴夫

@supertwang嗯。不完全是,因为那样我就必须停止使用我所做的所有花哨的定义,并清理代码以仅依赖标准库,这很痛苦。:您可以抢2参数版本stackoverflow.com/questions/15032594/...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.