Спустя 5 лет, есть ли что-то лучше, чем «Самые быстрые делегаты C ++»? - PullRequest
72 голосов
/ 28 ноября 2010

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

В общем, кажется, что Самый быстрый делегат Дона Клагстона - это первый выбор для многих. Есть несколько других популярных.

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

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

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

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

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

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

На сегодняшний день, какой рекомендуемый подход? Все еще используете версию Дона ? Или существует «консенсус сообщества» по поводу другого варианта?

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

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

Ответы [ 2 ]

120 голосов
/ 29 ноября 2010

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

Что ж, проблема с указателями наметоды в том, что они не все одинакового размера.Поэтому вместо того, чтобы хранить указатели на методы напрямую, нам нужно «стандартизировать» их, чтобы они имели постоянный размер.Это то, что Дон Клагстон пытается достичь в своей статье Code Project.Он делает это, используя глубокие знания самых популярных компиляторов.Я утверждаю, что это можно сделать в «обычном» 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 знает о типах, почему бы не поместить нашу функцию-обертку в качестве статическойфункция в этом?И поскольку в нем есть функция-обертка, почему бы не поместить в нее код для создания нашего 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, добавив безопасный 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))

Пример использования:

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 может быть членом более крупного класса, поскольку он имеет конструктор по умолчанию.Это также присваивается (функции сгенерированные компилятором функции копирования достаточно).Это также безопасно для типов благодаря шаблонам.

10 голосов
/ 29 ноября 2012

Я хотел бы продолжить ответ @ Insilico с некоторыми из моих собственных вещей.

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

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

ВАЖНОЕ ПРИМЕЧАНИЕ: На данный момент (28/11/2012, 21:35) Эта версия с вариадическим кодом не будет работать с ноябрьской CTP-версией Microsoft VC ++ 2012 (Милан). Если вы хотите использовать его с этим, вам придется избавиться от всего, что связано с переменным, и явно перечислить количество аргументов (и, возможно, шаблонизировать тип с 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;
        }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...