Programing

5 년 후, "가장 빠른 C ++ 델리게이트"보다 나은 것이 있습니까?

lottogame 2020. 10. 27. 07:44
반응형

5 년 후, "가장 빠른 C ++ 델리게이트"보다 나은 것이 있습니까?


나는 "C ++ 델리게이트"라는 주제가 끝났다는 것을 알고 있으며, http://www.codeproject.comhttp://stackoverflow.com 모두이 질문을 깊이 다루고 있습니다.

일반적으로 Don Clugston의 가장 빠른 대리인 이 많은 사람들에게 첫 번째 선택 인 것 같습니다 . 다른 몇 가지 인기있는 것들이 있습니다.

그러나 그 기사의 대부분이 2005 년경에 오래되었고 VC7과 같은 오래된 컴파일러를 고려하여 많은 디자인 선택이 이루어진 것 같다는 것을 알았습니다.

오디오 애플리케이션을위한 매우 빠른 델리게이트 구현이 필요합니다.

여전히 이식 가능해야하지만 (Windows, Mac, Linux) 최신 컴파일러 (VS2008 SP1 및 GCC 4.5.x에있는 VC9) 만 사용합니다.

내 주요 기준은 다음과 같습니다.

  • 빨라야합니다!
  • 최신 버전의 컴파일러와 호환되어야합니다. Don의 구현에 대해 약간 의문이 있습니다. 왜냐하면 그는 표준을 준수하지 않는다고 명시 적으로 언급했기 때문입니다.
  • 선택적으로 KISS 구문과 사용의 용이성은
  • 델리게이트 라이브러리를 중심으로 구축하는 것이 정말 쉽다고 확신하지만 멀티 캐스트는 좋을 것입니다.

게다가, 나는 정말로 이국적인 기능이 필요하지 않습니다. 방법에 대한 좋은 오래된 포인터가 필요합니다. 정적 메서드, 자유 함수 또는 이와 유사한 것을 지원할 필요가 없습니다.

오늘부터 권장되는 접근 방식은 무엇입니까? 여전히 Don의 버전을 사용 하십니까? 아니면 다른 옵션에 대한 "커뮤니티 합의"가 있습니까?

성능 측면에서 허용되지 않기 때문에 Boost.signal / signal2를 사용하고 싶지 않습니다. QT에 대한 종속성도 허용되지 않습니다.

또한, 예를 들어 cpp-events 와 같은 인터넷 검색 중에 일부 새로운 라이브러리를 보았지만 SO를 포함하여 사용자의 피드백을 찾을 수 없습니다.


업데이트 : 완전한 소스 코드와 더 자세한 논의가 포함 된 기사가 The Code Project에 게시되었습니다.

음, 메서드에 대한 포인터의 문제점은 모두 같은 크기가 아니라는 것입니다. 따라서 메서드에 대한 포인터를 직접 저장하는 대신, 일정한 크기가되도록 "표준화"해야합니다. 이것이 Don Clugston이 그의 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안전한 부울을 추가 하여 객체를 향상시킬 수 있습니다 . 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 ++ 컴파일러 (VS 2008과 함께 제공되는 버전 15.00.30729.01)에서 이것을 테스트했으며 코드를 사용하려면 최신 컴파일러가 필요합니다. 디스 어셈블리 검사를 통해 컴파일러는 래퍼 함수와 DeduceMemCallback호출 을 최적화 하여 간단한 포인터 할당으로 줄일 수있었습니다 .

콜백의 양쪽에 사용하는 것은 간단하며 표준 C ++ 만 사용합니다. 위에 표시된 코드는 인수가 1 개인 멤버 함수에 대해 작동하지만 더 많은 인수로 일반화 할 수 있습니다. 정적 함수에 대한 지원을 허용하여 더욱 일반화 할 수도 있습니다.

참고는 것을 Callback객체는 더 힙 할당을 필요로하지 않습니다 - 그들은이 "표준화"절차에 일정한 크기 덕분이다. 이 때문에 Callback기본 생성자 가 있으므로 객체가 더 큰 클래스의 구성원이 될 수 있습니다. 또한 할당 가능합니다 (컴파일러에서 생성 한 복사 할당 함수로 충분합니다). 템플릿 덕분에 형식이 안전합니다.


나는 약간의 내 물건으로 @Insilico의 대답을 따르고 싶었습니다.

이 답변을 우연히 발견하기 전에 오버 헤드가 발생하지 않고 함수 서명만으로 고유하게 비교 / 식별되는 빠른 콜백을 파악하려고했습니다. 내가 만든 것은 -BBQ에 있었던 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 (밀라노)에서 작동하지 않습니다. 그것과 함께 사용하려면 모든 가변 요소를 제거하고 인수의 수를 명시 적으로 열거해야합니다 (그리고 for에 Event대한 1- 인수 유형을 템플릿으로 전문화해야 함 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;
        }
};

참고 URL : https://stackoverflow.com/questions/4298408/5-years-later-is-there-something-better-than-the-fastest-possible-c-delegate

반응형