5 лет спустя, есть ли что-то лучше, чем "самые быстрые делегаты C++"?


Я знаю, что тема " делегатов C++" была сделана до смерти, и оба http://www.codeproject.com и http://stackoverflow.com глубоко осветите этот вопрос.

вообще, кажется, что Не Clugston быстро можно делегировать является первым выбором для многих людей. Есть несколько других популярных из них.

тем не менее, я заметил, что большинство этих статей старые (около 2005 года) и многие варианты дизайна, кажется, есть было сделано с учетом старых компиляторов, таких как VC7.

Мне нужна очень быстрая реализация делегата для аудио приложений.

Мне все еще нужно, чтобы он был портативным (Windows, Mac, Linux), но я использую только современные компиляторы (VC9, тот, что в VS2008 SP1 и GCC 4.5.икс.)

мои основные критерии:

  • это должно быть быстро!
  • он должен быть совместим с более новыми версиями компиляторов. У меня есть некоторые сомнения по этому поводу с реализацией Дона, потому что он явно заявляет, что он не соответствует стандарту.
  • дополнительно, поцелуй-синтаксис и простота в использовании приятно иметь
  • многоадресная рассылка была бы хороша, хотя я убежден, что ее очень легко построить вокруг любой библиотеки делегатов

кроме того, мне действительно не нужны экзотические функции. Мне просто нужен старый добрый указатель на метод. Нет необходимости поддерживать статические методы, свободные функции или такие вещи, как что.

на сегодняшний день, каков рекомендуемый подход? По-прежнему использовать Не версии? Или есть "консенсус сообщества" о другом варианте?

Я действительно не хочу использовать Boost.сигнал/signal2, потому что это не приемлемо с точки зрения производительности. Зависимость от Qt-это не приемлемо, а также.

кроме того, я видел некоторые новые библиотеки во время поиска в интернете, например cpp-события но я не мог найти никакой обратной связи от пользователей, в том числе и на так.

2 72

2 ответа:

обновление:в проекте кода размещена статья с полным исходным кодом и более детальным обсуждением.

Ну, проблема с указателями на методы заключается в том, что они не все одинакового размера. Поэтому вместо того, чтобы хранить указатели на методы напрямую, нам нужно "стандартизировать" их так, чтобы они имели постоянный размер. Это не 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)) {}

внутри функции, мы знаем, что R,T и A1 есть. Так что если мы можем построить структуру, которая может "держать" эти типы и их возврата из функции?

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 знает о типах, почему бы не поставить функцию как статическую функцию в нем? И поскольку функция-оболочка находится в нем, почему бы не поставить код чтобы построить наш

Я хотел следовать ответу @Insilico с небольшим количеством моих собственных вещей.

прежде чем я наткнулся на этот ответ, я пытался выяснить быстрые обратные вызовы, а также которые не несли никаких накладных расходов и были однозначно сопоставимы / идентифицированы только сигнатурой функции. То, что я в конечном итоге создал - с некоторой серьезной помощью от клингоны, которые оказались на барбекю - работает для всех типов функций (кроме лямбд, если вы не храните лямбда, но не пробуйте его, потому что это действительно трудно и трудно сделать, и может привести к роботу, доказывая вам, как это трудно, и заставляя вас есть дерьмо для него). Спасибо @sehe, @nixeagle, @StackedCrooked, @CatPlusPlus, @Xeo, @DeadMG и, конечно же, @Insilico за помощь в создании системы событий. Не стесняйтесь использовать, как вы хотите.

В любом случае, пример находится на ideone, но исходный код также здесь для вашего использования (потому что, поскольку Liveworkspace пошел вниз, я не доверяю им shady compiling сервисы. Кто знает, когда ideone пойдет вниз?!). Я надеюсь, что это полезно для тех, кто не занят лямбда/функция-возражая мир на куски:

важное примечание:на данный момент (28/11/2012, 9: 35 PM) эта вариативная версия не будет работать с Microsoft VC++ 2012 November CTP (Milan). Если вы хотите использовать его с этим, вам придется избавиться от всех вариативных вещей и явно перечислить количество аргументов (и, возможно, шаблон-специализировать тип 1-аргумента для Event на 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;
        }
};