В C# событие стиль и делегат в C++


Я ценю советы, касающиеся как деталь реализации и пути совершенствования использования/производительности/родовое.

Есть две части: XDelegate, который я получил от здесь, и XEvent, которая в случае реализации, похожий на C#.

#pragma once
#include <unordered_map>
//hashing function
//TODO: replace hashing with something else if it's too expensive
template<typename return_type, typename... params>
class XDelegate;
namespace std {
    template<typename return_type, typename... params>
    struct hash<XDelegate<return_type, params ...>> {
        typedef return_type(*Type)(void* callee, params...);

        static size_t hash_combine(size_t seed, size_t v)
        {
            return seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        }
        size_t operator()(const XDelegate<return_type, params ...> &pDelegate) const
        {
            size_t a = std::hash<void*>{}(pDelegate.fpCallee);
            size_t b = std::hash<Type>{}(pDelegate.fpCallbackFunction);
            return hash_combine(a, b);
        }
    };
};

//nice generic variadic delegate from http://blog.coldflake.com/posts/C++-delegates-on-steroids/
template<typename return_type, typename... params>
class XDelegate
{
    friend struct std::hash<XDelegate>;

    typedef return_type(*Type)(void* callee, params...);
public:
    XDelegate(void* callee, Type function)
        : _fpCallee(callee)
        , _fpCallbackFunction(function) {}
    template <class T, return_type(T::*TMethod)(params...)>
    static XDelegate from_function(T* callee)
    {
        XDelegate d(callee, &methodCaller<T, TMethod>);
        return d;
    }
    return_type operator()(params... xs) const
    {
        return (*_fpCallbackFunction)(_fpCallee, xs...);
    }
private:
    void* _fpCallee;
    Type _fpCallbackFunction;
    template <class T, return_type(T::*TMethod)(params...)>
    static return_type methodCaller(void* callee, params... xs)
    {
        T* p = static_cast<T*>(callee);
        return (p->*TMethod)(xs...);
    }
};


//C# style delegate-event for none ref class.
template<typename return_type, typename... params>
class XEvent
{
    friend struct std::hash<XDelegate<return_type, params ...>>;

public:
    size_t add(XDelegate<return_type, params ...> pDelegate)
    {
        size_t token = std::hash<XDelegate<return_type, params ...>>{}(pDelegate);
        auto result = _delegates.insert_or_assign(token, pDelegate);
#ifdef _DEBUG
        //if assignment took place, ie) there is a hash collision, throw exception.
        if (!result.second)
        {
            throw std::exception("XEvent hash collision");
        }
#endif
        return token;
    }
    void remove(size_t pToken)
    {
        _delegates.erase(pToken);
    }
    size_t size() const
    {
        return _delegates.size();
    }
    //on hindsight, I don't think C# events have return value?
    std::unordered_map<size_t, return_type> operator()(params... xs)const
    {
        std::unordered_map<size_t, return_type> result;
        for (auto itr = _delegates.begin(); itr != _delegates.end(); ++itr)
        {
            result[itr->first] = itr->second(xs...);

        }
        //return map of event tokens mapped to the results.
        return result;
    }
private:


    std::unordered_map<size_t, XDelegate<return_type, params ...>> _delegates;
};


template<typename... params>
class XEvent<void, params ...>
{
    friend struct std::hash<XDelegate<void, params ...>>;

public:
    size_t add(XDelegate<void, params ...> pDelegate)
    {
        size_t token = std::hash<XDelegate<void, params ...>>{}(pDelegate);
        auto result = _delegates.insert_or_assign(token, pDelegate);
#ifdef _DEBUG
        //if assignment took place, ie) there is a hash collision, throw exception.
        if (!result.second)
        {
            throw std::exception("XEvent hash collision");
        }
#endif
        return token;
    }
    void remove(size_t pToken)
    {
        _delegates.erase(pToken);
    }
    size_t size() const
    {
        return _delegates.size();
    }
    void operator()(params... xs)const
    {
        for (auto itr = _delegates.begin(); itr != _delegates.end(); ++itr)
        {
            itr->second(xs...);
        }
    }
private:
    std::unordered_map<size_t, XDelegate<void, params ...>> _delegates;
};

Это используется таким образом:

_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback1>(_tester)));
_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback2>(_tester)));
_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback3>(_tester)));


_returnEvent(L"Return event called!\n");

for (auto itr = _noReturnEventTokens.begin(); itr != _noReturnEventTokens.end(); ++itr)
{
    _noReturnEvent.remove(*itr);
}


231
1
задан 13 апреля 2018 в 07:04 Источник Поделиться
Комментарии
2 ответа

Ваше описание "используется такой" оставляет очень немного для воображения. Например, какие типы _noReturnEventTokens, _returnEventTokensи _returnEvent? Как они заявили? Что вы ожидаете произойдет , когда вы оцениваете _returnEvent(L"Return event called!\n")?


Я не думаю, что код настолько "неприятно" читать как CodeGorilla предложил, но она определенно может быть улучшена путем некоторых незначительных переформатирование. Я настоятельно рекомендую поставить пустую строку между каждой парой определений функций-членов: то есть, не

auto f()
{
}
auto g()
{
}

но вместо

auto f()
{
}

auto g()
{
}

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

auto f() {
}

auto g() {
}


void operator()(params... xs)const
{
for (auto itr = _delegates.begin(); itr != _delegates.end(); ++itr)
{
itr->second(xs...);
}
}

Первое, что нужно отметить в этом коде заключается в том, что он содержит старый стиль (без диапазон) for петли. Переписать это как

    for (auto&& pair : _delegates) {
pair.second(xs...);
}

Второе, что бросилось в глаза было построить params... xs. При этом параметр пакета по значению редкое, принимая на отлично-вперед (params&&... xs) является более распространенным. Но ведь вы делаете большое метапрограммирование здесь, может быть, к-значение является правильным. Поэтому я постоянно ищу, где params пришли — и я думаю, что все ваши использует приемлемые. Там было одно место, я думал, вы должны были с помощью идеально-переадресация, но потом я понял, что вы, вероятно, действительно нужно, чтобы избежать перемещения из аргументов преждевременно (поскольку они будут использоваться другими делегатами).

Кстати, я рекомендую CamelCase для параметров шаблона. То, что вы назвали params...я бы позвонила Args... или, возможно, просто Ts....


namespace std {
template<typename return_type, typename... params>
struct hash<XDelegate<return_type, params ...>> {
typedef return_type(*Type)(void* callee, params...);

static size_t hash_combine(size_t seed, size_t v)
{
return seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
size_t operator()(const XDelegate<return_type, params ...> &pDelegate) const
{
size_t a = std::hash<void*>{}(pDelegate.fpCallee);
size_t b = std::hash<Type>{}(pDelegate.fpCallbackFunction);
return hash_combine(a, b);
}
};
};

Что в прошлом }; должно быть просто }и я настоятельно рекомендую добавить комментарий // namespace std для удобочитаемости. Вы увидите этот "конец пространства имен комментария" конвенция используется в большинстве с++ программы, в эти дни.

Но для простого специализации std::hashвы на самом деле не нужно, чтобы открыть пространство имен — вы можете просто специализировать квалифицированного struct std::hashвот так:

template<class R, class... Args>
struct std::hash<XDelegate<R, Args...>> {
static size_t hash_combine(size_t seed, size_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}

size_t operator()(const XDelegate<R, Args...>& pDelegate) const {
using Type = R(void *, Args...);
size_t a = std::hash<void*>{}(pDelegate.fpCallee);
size_t b = std::hash<Type*>{}(pDelegate.fpCallbackFunction);
return hash_combine(a, b);
}
};

Соблюдать обнимаются скобки, пустые строки между функцией определения, верблюжьего шаблон имена параметров (и гораздо короче — return_type становится R), и модернизированный using T = U на месте typedef U T.

Заметим, что вместо объявления Type как псевдоним для функции-тип указателя, я объявил его как функция типа и перенесли * вниз в место его использовали, в std::hash<Type*>, что делает два std::hash<X*> вызовы красиво симметрично.

Заметить, что я никогда не ставил две побочных эффектов на той же строке кода. Вместо return seed ^= expr;я пишу seed ^= expr; и затем на следующей строке return (новое значение) seed. Это очень важно, когда вы начнете получать в атомной/замок-халява, но это никогда не помешает.


friend struct std::hash<XDelegate>;

Рассмотреть вопрос о предоставлении XDelegate нестатический член функции hash()так что если вы хотите, чтобы хэш-объекта x вы просто говорите x.hash()вместо std::hash<decltype(x)>{}(x). Вам все равно придется предоставить тривиальных внешних специализации

template<class R, class... Args>
struct std::hash<XDelegate<R, Args...>> {
size_t operator()(const XDelegate<R, Args...>& x) const {
return x.hash();
}
};

но он больше не надо быть friend класса.


Я уверен, что есть более событие и делегат-соответствующие виды вещей, чтобы говорить о, но это, где я остановлюсь. :)

1
ответ дан 15 апреля 2018 в 08:04 Источник Поделиться

Код не очень приятно читать, как оно было сгенерировано автоматически. Мне не хотелось бы забрать от вас и исправить ошибки в нем. Это может быть очень умный способ делать то, что вы пытаетесь сделать, но я не думаю, что его хороший код.

Она имеет очень мало на пути содержательные комментарии, не описать, что назначение шаблонов и функций. Если вы объедините это с ссылки на сайты по моему опыту это означает, что человек, пишущий это на самом деле не понимаю, что делает код.

Почему вы бросаете исключение в построениях отладки делает код никогда не ошибетесь в релизных сборках?

Один вопрос я не могу получить из моей головы если вы хотите c# делегаты и события почему бы не писать на C#, в противном случае просто использовать то, что уже есть в C++?

1
ответ дан 13 апреля 2018 в 11:04 Источник Поделиться