5 lat później, czy jest coś lepszego niż " najszybszy możliwy Język C++"?

Wiem, że temat "delegatów C++" został zrobiony na śmierć i obie http://www.codeproject.com i http://stackoverflow.com głęboko zakryj pytanie.

Ogólnie wydaje się, że najszybszy możliwy delegat Dona Clugstona jest pierwszym wyborem dla wielu ludzi. Jest kilka innych popularnych.

Zauważyłem jednak, że większość z tych artykułów jest stara (około 2005 roku) i wydaje się, że wiele wyborów projektowych zostało dokonanych z uwzględnieniem starych Kompilatory takie jak VC7.

Potrzebuję bardzo szybkiej implementacji dla aplikacji audio.

Nadal potrzebuję go przenośnego (Windows, Mac, Linux), ale używam tylko nowoczesnych kompilatorów (VC9, ten w VS2008 SP1 i GCC 4.5.x).

Moje główne kryteria to:

  • musi być szybko!
  • musi być kompatybilny z nowszymi wersjami kompilatorów. Mam pewne wątpliwości co do realizacji Dona, bo wyraźnie stwierdza, że nie jest zgodny ze standardami.
  • Jest to jeden z najbardziej znanych i cenionych w świecie gier komputerowych.]}
  • multicast byłby miły, chociaż jestem przekonany, że naprawdę łatwo jest zbudować go wokół dowolnej biblioteki delegatów

Poza tym nie potrzebuję egzotycznych funkcji. Potrzebuję tylko starej, dobrej metody. Nie ma potrzeby wspierania statycznych metod, darmowych funkcji czy tego typu rzeczy.

Na dzień dzisiejszy, jakie jest zalecane podejście? Nadal używasz wersji Dona? Lub jest jest "konsensus społeczności" co do innej opcji?

Naprawdę nie chcę używać Boost.signal / signal2, ponieważ jest to niedopuszczalne pod względem wydajności. Zależność od QT również nie jest akceptowalna.

Ponadto widziałem kilka nowszych bibliotek podczas googlowania, jak na przykład cpp-events, ale nie mogłem znaleźć żadnych opinii od użytkowników, w tym na temat SO.

Author: Jeffrey Bosboom, 2010-11-28

2 answers

Aktualizacja: na temat projektu Code został opublikowany artykuł z pełnym kodem źródłowym i bardziej szczegółową dyskusją.

Problem ze wskaźnikami do metod polega na tym, że nie wszystkie są tej samej wielkości. Zamiast więc przechowywać wskaźniki bezpośrednio do metod, musimy je "standaryzować" tak, aby miały stały rozmiar. To właśnie Don Clugston próbuje osiągnąć w swoim artykule Code Project. Robi to wykorzystując intymną wiedzę najbardziej popularnych Kompilatory. Twierdzę, że można to zrobić w "normalnym" C++, nie wymagając takiej wiedzy.

Rozważ następujący kod:

void DoSomething(int)
{
}

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

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

Jest to jeden ze sposobów implementacji wywołania zwrotnego za pomocą zwykłego starego wskaźnika funkcji. Nie działa to jednak w przypadku metod w obiektach. Naprawmy to:

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

Teraz mamy system wywołań zwrotnych, które mogą działać zarówno dla funkcji darmowych, jak i dla członków. Jest to jednak niezdarne i podatne na błędy. Istnieje jednak wzorzec-użycie funkcja wrapper "przekazuje" statyczne wywołanie funkcji do wywołania metody na właściwej instancji. W tym celu możemy użyć szablonów - spróbujmy uogólnić funkcję wrappera: {]}

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

Jest to nadal bardzo niezgrabne, ale przynajmniej teraz nie musimy wypisywać funkcji wrappera za każdym razem (przynajmniej dla Przypadku 1 argumentu). Inną rzeczą, którą możemy uogólnić, jest fakt, że zawsze przekazujemy wskaźnik void*. Zamiast przekazywać je jako dwie różne wartości, dlaczego nie poskładać je do kupy? A skoro już to robimy, to dlaczego nie uogólnić? Hej, dorzućmy operator()(), żebyśmy mogli nazwać to jak funkcję!

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;
}
Robimy postępy! Ale teraz naszym problemem jest fakt, że składnia jest absolutnie okropna. Składnia wydaje się zbędna; czy kompilator nie może określić typów ze wskaźnika do samej metody? Niestety nie, ale możemy pomóc. Pamiętaj, że kompilator może wydedukować typy poprzez dedukcję argumentów szablonu w funkcji sprawdzam. Może zaczniemy od tego?
template<typename R, class T, typename A1>
void DeduceMemCallback(R (T::*)(A1)) {}

Wewnątrz funkcji wiemy, co R, T i A1 jest. Co z tego, że możemy zbudować strukturę, która może "trzymać" te typy i zwracać je z funkcji?

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

A ponieważ DeduceMemCallbackTag wie o typach, dlaczego nie umieścić w nim naszej funkcji wrappera jako funkcji statycznej? A skoro jest w nim funkcja wrapper, dlaczego nie umieścić w nim kodu do konstruowania naszego Callback obiektu?

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

Standard C++ pozwala na wywołanie funkcje statyczne na instancjach (!):

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

Mimo to jest to długie wyrażenie, ale możemy to umieścić w prostym makrze (!):

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

Możemy ulepszyć obiekt Callback dodając Bezpieczny bool. Dobrym pomysłem jest również wyłączenie operatorów równości, ponieważ nie można porównywać dwóch Callback obiektów. Jeszcze lepiej jest użyć częściowej specjalizacji, aby umożliwić "preferowaną składnię". To daje nam:

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

Przykład użycia:

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

Przetestowałem to na Visual C++ compiler (Wersja 15.00.30729.01, ta, która jest dostarczana z VS 2008), i potrzebujesz raczej najnowszego kompilatora, aby użyć kodu. Dzięki kontroli demontażu kompilator był w stanie zoptymalizować funkcję wrappera i wywołanie DeduceMemCallback, redukując się do prostych przydziałów wskaźników.

Jest prosty w użyciu dla obu stron wywołania zwrotnego i używa tylko (co uważam za) standard C++. Kod, który pokazałem powyżej działa dla funkcji Członkowskich z 1 argumentem, ale może być uogólnione na więcej argumentów. Można go również dodatkowo uogólnić, umożliwiając obsługę funkcji statycznych.

Zauważ, że obiekt Callback nie wymaga alokacji sterty - mają one stały rozmiar dzięki tej procedurze "standaryzacji". Z tego powodu możliwe jest, aby obiekt Callback był członkiem większej klasy, ponieważ posiada domyślny konstruktor. Jest on również przypisywalny (wystarczające są funkcje przypisywania kopii generowanych przez kompilator). Jest również typowa, dzięki szablony.

 119
Author: In silico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-15 16:34:09

Chciałem śledzić odpowiedź @Insilico z odrobiną moich własnych rzeczy.

Zanim natknąłem się na tę odpowiedź, starałem się również rozgryźć szybkie wywołania zwrotne, które nie wiązały się z kosztami i były jednoznacznie porównywalne / identyfikowane tylko za pomocą podpisu funkcji. To, co stworzyłem-z poważną pomocą Klingonów, którzy akurat byli na grillu - działa dla wszystkich typów funkcji (oprócz Lambda, chyba że przechowujesz Lambda, ale nie próbuj, bo to naprawdę trudne i trudne do zrobienia i może spowodować, że robot udowodni ci, jak trudne to jest i zmusi cię do zjedzenia gówna za to). Podziękowania dla @sehe, @ nixeagle, @StackedCrooked, @ CatPlusPlus, @Xeo, @DeadMG i oczywiście @Insilico za pomoc w tworzeniu systemu zdarzeń. Możesz używać, jak chcesz.

W każdym razie, przykład jest na ideone, ale kod źródłowy jest również tutaj do użytku (Bo, ponieważ Liveworkspace poszedł dół, nie ufam im shady usługi kompilacji. Kto wie kiedy ideone upadnie?!). Mam nadzieję, że jest to przydatne dla kogoś, kto nie jest zajęty Lambda / Function-sprzeciwianie się światu na kawałki:

WAŻNA UWAGA :od teraz (28/11/2012, 21:35) ta wersja nie będzie działać z Microsoft VC++ 2012 November CTP (Milan). Jeśli chcesz go używać z tym, będziesz musiał pozbyć się wszystkich zmiennych rzeczy i jawnie wyliczyć liczbę argumentów (i ewentualnie szablon-specjalizacja typu 1-argument dla Event dla void), aby to działało. To jest ból, i udało mi się tylko napisać to dla 4 argumentów, zanim się zmęczyłem (i uznałem, że przekazanie więcej niż 4 argumentów było trochę naciągane).

Przykład Źródłowy

Źródło:

#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;
        }
};
 10
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-28 21:27:28