2016-04-16 5 views
14

यह उत्तर के साथ प्रश्न है, जिसका उद्देश्य पाठकों को अपने स्वयं के समाधान सुझाए जाने के लिए आमंत्रित करना है।
मुझे पूरा यकीन है कि वहां से मेरे पास अधिक चालाक दृष्टिकोण हैं, इसलिए मैं जानना चाहता हूं कि ये समाधान क्या हैं।
कृपया, अपना जवाब जोड़कर अपना ज्ञान साझा करें !!
श्रोताओं के रूप में इवेंट एमिटर और सदस्य विधियों के स्वचालित पंजीकरण


लक्ष्य एक emitter वर्ग है कि कुछ घटनाओं प्रेषण करने के लिए इस्तेमाल किया जा सकता बनाने के लिए है।

एक महत्वपूर्ण विशेषता जो मैं एमिटर में बनना चाहता हूं वह श्रोताओं को एमिटर को संलग्न करने के लिए पंजीकरण सुविधा का उपयोग करने में आसान है।

दूसरे शब्दों में, मैं सभी श्रोताओं को एमिटर को संलग्न करने के उद्देश्य से कार्यों/विधियों को लिखना नहीं चाहता हूं, क्योंकि यह त्रुटि प्रवण हो सकती है और मैंने खुद को एक बार एक बग की तलाश में पाया जो कि उस वजह से कोड की मिस्ड लाइन (एक पंक्ति जो एन-वें श्रोता पंजीकृत है)।

कल्पना कीजिए निम्नलिखित structs:

struct E1 { }; 

struct S { 
    void receive(const E1 &ev) { /* do something */ } 
}; 

पंजीकरण सुविधा के लिए मैं देख रहा हूँ इस तरह के एक समाधान है, जिसके लिए कोड की निम्न लाइनों पर्याप्त हैं:

S s; 
emitter.reg(s); 

और बस इतना ही है, यहां तक ​​कि यदि भविष्य में यह एक और श्रोता के लिए संरचना S में जोड़ा जाना आवश्यक है, उदाहरण के लिए:

struct E1 { }; 
struct E2 { }; 

struct S { 
    void receive(const E1 &ev) { /* do something */ } 
    void receive(const E2 &ev) { /* do something */ } 
}; 

मैं ऐसे एमिटर कैसे लिख सकता हूं?

+1

एर .. आप वास्तव में क्या करने के लिए 'emitter.reg (ओं) चाहते हैं? और वैसे भी 'emitter' क्या है? क्या यह एक वर्ग टेम्पलेट का एक उदाहरण है? क्या आप सभी 'ई 1',' ई 2', ... प्रकार पहले से जानते हैं? – Barry

+0

अधिक या कम। अगर मुझे 'एमिटर <ई 1, ई 2, एंडसोऑन>' जैसी परिभाषा चाहिए तो मुझे उन्हें अवश्य जानना चाहिए। कक्षा टेम्पलेट? शायद, यह वास्तविक कार्यान्वयन पर निर्भर करता है, यही वह है जिसे मैंने एक उत्तर के रूप में प्रस्तावित किया। – skypjack

+0

मैंने एक छोटी पुस्तकालय लिखी जो एक बहुत ही समान समस्या को संबोधित करती है। Tldr ग्राहक प्रकार पर टाइप-एरर का उपयोग करना है और टाइप-मिटाए गए ग्राहकों को std :: type_index द्वारा इंडेक्स करना है। Lib है https://github.com/mmcshane/eventbus। मैं आत्म-प्रचार की उपस्थिति से बचने के उत्तर के बजाय एक टिप्पणी के रूप में पोस्ट कर रहा हूं। – mpm

उत्तर

2

यहाँ यह एक संभव emitter वर्ग दृढ़ता से टेम्पलेट्स और sfinae पर आधारित है कि की एक पूरी उदाहरण इस प्रकार है।
कोई इसे g++ -g -std=c++14 main.cpp लाइन का उपयोग करके संकलित कर सकता है।
मैंने कोड को कम से कम उदाहरण में कम करने की कोशिश की, इस प्रकार नग्न पॉइंटर्स और केवल कुछ सदस्य विधियों का उपयोग किया।

#include <functional> 
#include <vector> 
#include <cassert> 

template<class E> 
struct ETag { using type = E; }; 

template<int N, int M> 
struct Choice: public Choice<N+1, M> { }; 

template<int N> 
struct Choice<N, N> { }; 

template<int S, class... T> 
class Base; 

template<int S, class E, class... O> 
class Base<S, E, O...>: public Base<S, O...> { 
    using OBase = Base<S, O...>; 

protected: 
    using OBase::get; 
    using OBase::reg; 

    std::vector<std::function<void(const E &)>>& get(ETag<E>) { 
     return vec; 
    } 

    template<class C> 
    auto reg(Choice<S-(sizeof...(O)+1), S>, C* ptr) 
    -> decltype(std::declval<C>().receive(std::declval<E>())) { 
     using M = void(C::*)(const E &); 
     M m = &C::receive; 
     std::function<void(const E &)> fn = std::bind(m, ptr, std::placeholders::_1); 
     vec.emplace_back(fn); 
     OBase::reg(Choice<S-sizeof...(O), S>{}, ptr); 
    } 

private: 
    std::vector<std::function<void(const E &)>> vec; 
}; 

template<int S> 
class Base<S> { 
protected: 
    virtual ~Base() { } 
    void get(); 
    void reg(Choice<S, S>, void*) { } 
}; 

template<class... T> 
class Emitter: public Base<sizeof...(T), T...> { 
    using EBase = Base<sizeof...(T), T...>; 

public: 
    template<class C> 
    void reg(C *ptr) { 
     EBase::reg(Choice<0, sizeof...(T)>{}, ptr); 
    } 

    template<class E, class... A> 
    void emit(A&&... args) { 
     auto &vec = EBase::get(ETag<E>{}); 
     E e(std::forward<A>(args)...); 
     for(auto &&fn: vec) fn(e); 
    } 
}; 

struct E1 { }; 
struct E2 { }; 
struct E3 { }; 

struct S { 
    void receive(const E1 &) { e1 = !e1; } 
    void reject(const E2 &) { e2 = !e2; } 
    void receive(const E3 &) { e3 = !e3; } 
    void check() { assert(e1); assert(e2); assert(e3); } 
    bool e1{false}; 
    bool e2{true}; 
    bool e3{false}; 
}; 

int main() { 
    S s; 
    Emitter<E1, E2, E3> emitter; 

    emitter.reg(&s); 
    emitter.emit<E1>(); 
    emitter.emit<E2>(); 
    emitter.emit<E3>(); 
    s.check(); 
} 
7

पहले में शामिल हैं:

#include <iostream> 
#include <vector> 
#include <type_traits> 
#include <utility> 
#include <functional> 

हम का उपयोग void_tdetection helper:

template<class ...> 
using void_t = void; 

हम void_t का उपयोग कर तरीकों का पता लगाने के लिए एक विशेषता को परिभाषित:

template<class C, class E, class X = void_t<>> 
struct has_event_handler : 
     std::false_type {}; 

template<class C, class E> 
struct has_event_handler<C, E, void_t< decltype(
    std::declval<C>().receive(std::declval<const E>()) 
) >> : std::true_type {}; 

template<class C, class E> 
constexpr bool has_event_handler_v = has_event_handler<C, E>::value; 

इसका उपयोग करके, हम एमिटर क्लास को परिभाषित कर सकते हैं।variadic तर्क घटनाओं के प्रकार यह प्रबंधन कर सकते हैं कर रहे हैं:

template<class...> class Emitter; 

// Recursive case: 
template<class E, class... F> 
class Emitter<E, F...> : Emitter<F...> { 
public: 
    // Register: 
    template<class C> 
    std::enable_if_t<!has_event_handler_v<C,E>> reg(C& callback) { 
    Emitter<F...>::reg(callback); 
    }; 
    template<class C> 
    std::enable_if_t<has_event_handler_v<C,E>> reg(C& callback) { 
    handlers_.push_back([&callback](E const& event) { return callback.receive(event); }); 
    Emitter<F...>::reg(callback); 
    }; 
    void trigger(E const& event) 
    { 
    for (auto const& handler : handlers_) 
     handler(event);  
    } 
    template<class G> 
    void trigger(G const& event) 
    { 
    Emitter<F...>::trigger(event); 
    } 
private: 
    std::vector<std::function<void(const E&)>> handlers_; 
}; 

// Base case: 
template<> 
class Emitter<> { 
public: 
    template<class C> 
    void reg(C& callback) {}; 
    template<class E> 
    void trigger(E const& event) 
    { 
    static_assert(!std::is_same<E,E>::value, 
     "Does not handle this type of event."); 
    } 
}; 

trigger() भाग के लिए, एक और समाधान std::enable_if_t<std::is_base_of_v<E, G>> उपयोग करने के लिए किया जाएगा।

और हम के साथ उपयोग कर सकते हैं:

// Events 
struct E1 {}; 
struct E2 {}; 
struct E3 {}; 

// Handler 
struct handler { 
    void receive(const E1&) 
    { 
    std::cerr << "E1\n"; 
    } 
    void receive(const E2&) 
    { 
    std::cerr << "E2\n"; 
    } 
}; 

// Check the trait: 
static_assert(has_event_handler_v<handler, E1>, "E1"); 
static_assert(has_event_handler_v<handler, E2>, "E2"); 
static_assert(!has_event_handler_v<handler, E3>, "E3"); 

int main() 
{ 
    Emitter<E1, E2> emitter; 
    handler h; 
    emitter.reg(h); 
    emitter.trigger(E1()); 
    emitter.trigger(E2()); 
} 

नोट: मैं के साथ सी ++ 11 क्रम एक छोटा कोड पाने के लिए, लेकिन के लिए संगतता सी ++ 17 से _v और _t वेरिएंट इस्तेमाल किया आप शायद struct संस्करणों (typename std::enable_if<foo>::type, std::is_base_of<B,D>::value, आदि का उपयोग करना चाहें)।

अद्यतन: यह शायद Emitter की पुनरावर्ती मामले के लिए बजाय रचना विरासत उपयोग करना बेहतर है: विरासत के बिना

template<class E, class... F> 
class Emitter<E, F...> { 
public: 
    // Register: 
    template<class C> 
    std::enable_if_t<!has_event_handler_v<C,E>> reg(C& callback) { 
    Emitter<F...>::reg(callback); 
    }; 
    template<class C> 
    std::enable_if_t<has_event_handler<C,E>::value> reg(C& callback) { 
    handlers_.push_back([&callback](E const& event) { return callback.receive(event); }); 
    emitter_.reg(callback); 
    }; 
    void trigger(E const& event) 
    { 
    for (auto const& handler : handlers_) 
     handler(event);  
    } 
    template<class G> 
    void trigger(G const& event) 
    { 
    emitter_.trigger(event); 
    } 
private: 
    std::vector<std::function<void(const E&)>> handlers_; 
    Emitter<F...> emitter_; 
}; 
3

मेरे संस्करण:

template <typename C, typename E> std::false_type has_event_handler_impl(...); 
template <typename C, typename E> 
auto has_event_handler_impl(int) 
-> decltype(static_cast<void>(std::declval<C>().receive(std::declval<const E>())), 
      std::true_type{}); 

template <typename C, typename E> 
using has_event_handler = decltype(has_event_handler_impl<C, E>(0)); 

template <class... Es> 
class Emitter { 
public: 

    template<class C> 
    void reg(C& callback) { 
     const int dummy[] = { 0, (regT<Es>(callback), 0)...}; 
     static_cast<void>(dummy); // Avoid unused variable warning 
    } 

    template <typename E> 
    void emit(const E& event) 
    { 
     for (auto const& handler : get_vector<E>()) { 
      handler(event); 
     } 
    } 

private: 
    template <typename E, typename C> 
    std::enable_if_t<has_event_handler<C, E>::value> 
    regT(C& callback) 
    { 
     auto lambda = [&callback](const E& event) { return callback.receive(event); }; 
     get_vector<E>().push_back(lambda); 
    } 

    template <typename E, typename C> 
    std::enable_if_t<!has_event_handler<C, E>::value> 
    regT(C&) 
    { 
     /* Empty */ 
    } 

    template <typename E> 
    std::vector<std::function<void(const E&)>>& get_vector() 
    { 
     return std::get<std::vector<std::function<void(const E&)>>>(handlers_); 
    } 

private: 
    std::tuple<std::vector<std::function<void(const Es&)>>...> handlers_; 
}; 

Demo

+0

टुपल्स का उपयोग करने का विचार अच्छा है। – ysdx

संबंधित मुद्दे