5年后,还有比"最快可能的C++代表"更好的东西吗?

Din*_*aiz 72 c++ performance delegates

我知道"C++代表"的主题已经完成,而http://www.codeproject.comhttp://stackoverflow.com都深深地涵盖了这个问题.

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

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

我需要一个非常快速的代理实现音频应用程序.

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

我的主要标准是:

  • 一定要快!
  • 它必须与更新版本的编译器向前兼容.我对Don的实施有一些疑问,因为他明确表示它不符合标准.
  • 可选地,KISS语法和易用性很好
  • 多播会很好,虽然我确信在任何委托库中构建它真的很容易

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

截至今天,推荐的方法是什么?仍然使用唐的版本?或者是否存在关于另一种选择的"社区共识"?

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

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

In *_*ico 120

更新: 在"代码项目"上发布了一篇包含完整源代码和更详细讨论的文章.

嗯,指向方法的问题是它们的大小不一样.因此,我们不需要直接存储指向方法的指针,而是需要对它们进行"标准化",使它们具有恒定的大小.这就是Don Clugston在他的Code Project文章中试图实现的目标.他使用最流行的编译器的亲密知识这样做.我声称可以在"普通"C++中进行,而不需要这些知识.

请考虑以下代码:

void DoSomething(int)
{
}

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

int main()
{
    InvokeCallback(&DoSomething);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

这仍然非常笨拙,但至少现在我们不必每次都写出一个包装函数(至少对于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;
}
Run Code Online (Sandbox Code Playgroud)

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

template<typename R, class T, typename A1>
void DeduceMemCallback(R (T::*)(A1)) {}
Run Code Online (Sandbox Code Playgroud)

在函数内部,我们知道什么R,TA1是.那么如果我们可以构造一个可以"保持"这些类型并从函数中返回它们的结构呢?

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>();
}
Run Code Online (Sandbox Code Playgroud)

既然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>();
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

不过,这是一个冗长的表达式,但我们可以把它放到一个简单的宏(!)中:

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;
}
Run Code Online (Sandbox Code Playgroud)

我们可以Callback通过添加安全bool 来增强对象.禁用相等运算符也是一个好主意,因为无法比较两个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))
Run Code Online (Sandbox Code Playgroud)

用法示例:

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;
}
Run Code Online (Sandbox Code Playgroud)

我已经在Visual C++编译器(版本15.00.30729.01,VS 2008附带的版本)上对此进行了测试,并且您确实需要一个相当新的编译器来使用该代码.通过检查反汇编,编译器能够优化包装函数和DeduceMemCallback调用,减少到简单的指针赋值.

它很容易用于回调的两面,并且仅使用(我相信的)标准C++.我上面显示的代码适用于带有1个参数的成员函数,但可以推广到更多参数.通过允许支持静态函数,还可以进一步推广它.

请注意,该Callback对象不需要堆分配 - 由于这种"标准化"过程,它们具有恒定的大小.因此,可以让Callback对象成为更大类的成员,因为它具有默认构造函数.它也是可分配的(编译器生成的拷贝分配功能就足够了).由于模板,它也是类型安全的.

  • @John:实际上有一种方法可以通过[利用一个定义规则]使这些代表具有可比性[http://stackoverflow.com/questions/7670000/addresses-of-identical-function-template-instantiations-across-编译单位).当我写这篇文章时,我不知道ODR适用于获取功能模板的地址(注意链接的问题比我的要晚.)我做了一些测试,似乎工作.您需要做的就是比较函数指针和对象指针.我有空的时候可能会在将来更新这个答案和文章. (3认同)
  • @Dinaiz:0-5参数版本与上面几乎完全相同,但是像'DeduceMemCallbackTag5`这样的参数如'A1`,'A2`,`A3`等等.部分模板专业化业务使它透明化.它确实爆炸成数百行代码,这使它成为一个独立的库的良好候选者.我现在有点忙,但后来我会用完整的源代码写一篇文章.此Stack Overflow答案足够长. (2认同)
  • @j_random_hacker:好问题.`Wrapper <>()`函数需要在编译时**指向方法**,因此用户代码必须通过**非类型**模板参数提供方法指针,即唯一的非类型模板参数`Bind <>()`.`DeduceMemCallback()`的唯一目的是推导构成方法指针的类型,返回一个"知道"过程中推断出的类型的虚拟对象.这允许`Bind <>()`接受方法指针作为非类型参数,而不必显式接受构成方法指针的类型. (2认同)
  • 这是我在 S/O 上见过的最史诗般的答案。太棒了! (2认同)

小智 10

我想用一些我自己的东西来关注@ Insilico的回答.

在我偶然发现这个答案之前,我试图找出快速回调,它没有产生任何开销,只能通过功能签名进行唯一比较/识别.我最终创造的东西 - 在Klingons发生烧烤的一些严肃的帮助- 适用于所有功能类型(除了Lambdas,除非你存储Lambda,但不要尝试它,因为它真的很难,很难做到和可能会导致一个机器人向你证明它是多么困难,并让你为它吃屎).感谢@sehe,@ nixeagle,@ StackedCrooked,@ CatPlusPlus,@ Xeo,@ DeadMG以及@Insilico在创建活动系统方面的帮助.随意使用,随意使用.

无论如何,一个例子是关于ideone,但是源代码也在这里供你使用(因为,因为Liveworkspace失败了,我不相信他们阴暗的编译服务.谁知道ideone何时会崩溃?!).我希望这对于那些不忙于Lambda/Function的人来说是有用的 - 将世界分解为碎片:

重要说明:截至目前(2012年11月28日,下午9:35)此可变版本不适用于Microsoft VC++ 2012 November CTP(米兰).如果你想使用它,你将不得不摆脱所有可变参数的东西,并明确枚举参数的数量(并可能模板 - 专门为1参数类型Eventfor void),使其工作.这是一种痛苦,在我累了之前我只能设法写出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;
        }
};
Run Code Online (Sandbox Code Playgroud)