2012-11-27 3 views
33

सी ++ 11 में किए गए परिवर्तनों के साथ (जैसे std::bind को शामिल करना), क्या वहां कोई है मूल भाषा या मानक पुस्तकालय (जैसे boost::signal) के बाहर किसी भी चीज़ पर निर्भरता के बिना एक साधारण सिंगल-थ्रेडेड पर्यवेक्षक पैटर्न को लागू करने के लिए अनुशंसित तरीका?सी ++ 11 पर्यवेक्षक पैटर्न (संकेत, स्लॉट, घटनाएं, ब्रॉडकास्टर/श्रोता, या जिसे आप इसे कॉल करना चाहते हैं)

संपादित

कोई कुछ दिखा कैसे boost::signal पर निर्भरता नई भाषा सुविधाओं का उपयोग कम किया जा सकता कोड पोस्ट कर सकता है, कि अभी भी बहुत उपयोगी होगा।

+3

std :: function का वेक्टर बनाएं? बूस्ट :: सिग्नल के साथ क्या गलत है कि आप सी ++ 11 फीचर्स के साथ ठीक करना चाहते हैं? –

+0

'अनुशंसित' ... एक मतदान की तरह लगता है :) – xtofl

+6

@xtofl मुझे मुहावरे के लिए बहुत उपयोगी प्रश्न पूछते हैं। –

उत्तर

28

मुझे लगता है कि bind यह आसान ब्लॉक बनाना है कि बनाता है (सीएफआर 'preferred' syntax vs. the 'portable' syntax -। वह सब दूर जा रहा है)। हालांकि, पर्यवेक्षक प्रबंधन कम जटिल नहीं हो रहा है।

लेकिन @ आर के रूप में। मार्टिनो फर्नांडीस का उल्लेख है: std::vector<std::function< r(a1) > > अब आसानी से (कृत्रिम) 'शुद्ध वर्चुअल' इंटरफेस वर्ग के लिए परेशानी के बिना बनाया गया है।


अनुरोध पर

: कनेक्शन प्रबंधन पर एक विचार - शायद कीड़े से भरा है, लेकिन आप अनुमान लगा सकेंगे कि:

// note that the Func parameter is something 
// like std::function< void(int,int) > or whatever, greatly simplified 
// by the C++11 standard 
template<typename Func> 
struct signal { 
    typedef int Key; // 
    Key nextKey; 
    std::map<Key,Func> connections; 

    // note that connection management is the same in C++03 or C++11 
    // (until a better idea arises) 
    template<typename FuncLike> 
    Key connect(FuncLike f) { 
    Key k=nextKey++; 
    connections[k]=f; 
    return k; 
    } 

    void disconnect(Key k){ 
    connections.erase(k); 
    } 

    // note: variadic template syntax to be reviewed 
    // (not the main focus of this post) 
    template<typename Args...> 
    typename Func::return_value call(Args... args){ 
    // supposing no subcription changes within call: 
    for(auto &connection: connections){ 
     (*connection.second)(std::forward(...args)); 
    } 
    } 
}; 

उपयोग:

signal<function<void(int,int)>> xychanged; 

void dump(int x, int y) { cout << x << ", " << y << endl; } 

struct XY { int x, y; } xy; 

auto dumpkey=xychanged.connect(dump); 
auto lambdakey=xychanged.connect([&xy](int x, int y){ xy.x=x; xy.y=y; }); 

xychanged.call(1,2); 
+3

श्रोता/पर्यवेक्षक के पास दो भाग हैं - संदेश कैसे भेजें (जो 'std :: function' ठीक है), और संदेशों को सुनना नहीं चाहते हैं, इसके बाद कैसे अलग किया जाए। वह दूसरा हिस्सा करने के लिए सिरदर्द का थोड़ा सा हिस्सा है। – Yakk

+0

@Yakk: वास्तव में। 'बूस्ट :: सिग्नल' एक 'कनेक्शन' ऑब्जेक्ट देता है जिसका आप उपयोग कर सकते हैं। – xtofl

+0

@xtofl - क्या आप एक सरल कोड उदाहरण पोस्ट कर सकते हैं यह दिखाते हुए कि आप जितना संभव हो सके बूस्ट सिग्नल के आधार पर यह कैसे कर सकते हैं (यानी शायद इसे संलग्न/अलग करने की कार्यक्षमता के लिए उपयोग कर रहे हैं)। मैं उत्सुक हूँ। – learnvst

0

मैं एक जाना पड़ा है इस पर भी। इस प्रयास में मेरे प्रयास मिल सकते हैं, जो विकसित हो रहे हैं। । ।

https://gist.github.com/4172757

मैं एक अलग शैली, बूस्ट संकेतों की तुलना में Juce में परिवर्तन सूचनाओं के समान का उपयोग करें। कनेक्शन प्रबंधन कुछ लैम्ब्डा वाक्यविन्यास का उपयोग करके किया जाता है जो प्रतिलिपि द्वारा कुछ कैप्चर करता है। यह अब तक अच्छी तरह से काम कर रहा है।

2

मैंने अपना खुद का हल्का वजन सिग्नल/स्लॉट कक्षाएं लिखीं जो कनेक्शन हैंडल लौटाती हैं। मौजूदा उत्तर की कुंजी प्रणाली अपवादों के सामने बहुत कमजोर है। स्पष्ट कॉल के साथ चीजों को हटाने के बारे में आपको असाधारण रूप से सावधान रहना होगा। मैं खुले/करीबी जोड़े के लिए RAII का उपयोग करना पसंद करता हूं।

मेरी लाइब्रेरी में समर्थन की एक उल्लेखनीय कमी आपकी कॉल से वापसी मूल्य प्राप्त करने की क्षमता है। मेरा मानना ​​है कि बूस्ट :: सिग्नल में कुल रिटर्न वैल्यू की गणना करने के तरीके हैं। अभ्यास में आमतौर पर आपको इसकी आवश्यकता नहीं होती है और मुझे बस यह अव्यवस्था मिलती है, लेकिन मैं भविष्य में एक अभ्यास के रूप में मस्ती के लिए ऐसी वापसी विधि के साथ आ सकता हूं।

मेरी कक्षाओं के बारे में एक अच्छी बात स्लॉट और स्लॉट रजिस्ट्रार कक्षाएं हैं। SlotRegister एक सार्वजनिक इंटरफ़ेस प्रदान करता है जिसे आप सुरक्षित रूप से एक निजी स्लॉट से लिंक कर सकते हैं। यह आपके पर्यवेक्षक तरीकों को बुलाते हुए बाहरी वस्तुओं के खिलाफ सुरक्षा करता है। यह आसान है, लेकिन अच्छा encapsulation।

मुझे विश्वास नहीं है कि मेरा कोड थ्रेड सुरक्षित है, हालांकि।

//"MIT License + do not delete this comment" - M2tM : http://michaelhamilton.com 

#ifndef __MV_SIGNAL_H__ 
#define __MV_SIGNAL_H__ 

#include <memory> 
#include <utility> 
#include <functional> 
#include <vector> 
#include <set> 
#include "Utility/scopeGuard.hpp" 

namespace MV { 

    template <typename T> 
    class Signal { 
    public: 
     typedef std::function<T> FunctionType; 
     typedef std::shared_ptr<Signal<T>> SharedType; 

     static std::shared_ptr< Signal<T> > make(std::function<T> a_callback){ 
      return std::shared_ptr< Signal<T> >(new Signal<T>(a_callback, ++uniqueId)); 
     } 

     template <class ...Arg> 
     void notify(Arg... a_parameters){ 
      if(!isBlocked){ 
       callback(std::forward<Arg>(a_parameters)...); 
      } 
     } 
     template <class ...Arg> 
     void operator()(Arg... a_parameters){ 
      if(!isBlocked){ 
       callback(std::forward<Arg>(a_parameters)...); 
      } 
     } 

     void block(){ 
      isBlocked = true; 
     } 
     void unblock(){ 
      isBlocked = false; 
     } 
     bool blocked() const{ 
      return isBlocked; 
     } 

     //For sorting and comparison (removal/avoiding duplicates) 
     bool operator<(const Signal<T>& a_rhs){ 
      return id < a_rhs.id; 
     } 
     bool operator>(const Signal<T>& a_rhs){ 
      return id > a_rhs.id; 
     } 
     bool operator==(const Signal<T>& a_rhs){ 
      return id == a_rhs.id; 
     } 
     bool operator!=(const Signal<T>& a_rhs){ 
      return id != a_rhs.id; 
     } 

    private: 
     Signal(std::function<T> a_callback, long long a_id): 
      id(a_id), 
      callback(a_callback), 
      isBlocked(false){ 
     } 
     bool isBlocked; 
     std::function<T> callback; 
     long long id; 
     static long long uniqueId; 
    }; 

    template <typename T> 
    long long Signal<T>::uniqueId = 0; 

    template <typename T> 
    class Slot { 
    public: 
     typedef std::function<T> FunctionType; 
     typedef Signal<T> SignalType; 
     typedef std::shared_ptr<Signal<T>> SharedSignalType; 

     //No protection against duplicates. 
     std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){ 
      if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){ 
       auto signal = Signal<T>::make(a_callback); 
       observers.insert(signal); 
       return signal; 
      } else{ 
       return nullptr; 
      } 
     } 
     //Duplicate Signals will not be added. If std::function ever becomes comparable this can all be much safer. 
     bool connect(std::shared_ptr<Signal<T>> a_value){ 
      if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){ 
       observers.insert(a_value); 
       return true; 
      }else{ 
       return false; 
      } 
     } 

     void disconnect(std::shared_ptr<Signal<T>> a_value){ 
      if(!inCall){ 
       observers.erase(a_value); 
      } else{ 
       disconnectQueue.push_back(a_value); 
      } 
     } 

     template <typename ...Arg> 
     void operator()(Arg... a_parameters){ 
      inCall = true; 
      SCOPE_EXIT{ 
       inCall = false; 
       for(auto& i : disconnectQueue){ 
        observers.erase(i); 
       } 
       disconnectQueue.clear(); 
      }; 

      for (auto i = observers.begin(); i != observers.end();) { 
       if (i->expired()) { 
        observers.erase(i++); 
       } else { 
        auto next = i; 
        ++next; 
        i->lock()->notify(std::forward<Arg>(a_parameters)...); 
        i = next; 
       } 
      } 
     } 

     void setObserverLimit(size_t a_newLimit){ 
      observerLimit = a_newLimit; 
     } 
     void clearObserverLimit(){ 
      observerLimit = std::numeric_limits<size_t>::max(); 
     } 
     int getObserverLimit(){ 
      return observerLimit; 
     } 

     size_t cullDeadObservers(){ 
      for(auto i = observers.begin(); i != observers.end();) { 
       if(i->expired()) { 
        observers.erase(i++); 
       } 
      } 
      return observers.size(); 
     } 
    private: 
     std::set< std::weak_ptr< Signal<T> >, std::owner_less<std::weak_ptr<Signal<T>>> > observers; 
     size_t observerLimit = std::numeric_limits<size_t>::max(); 
     bool inCall = false; 
     std::vector< std::shared_ptr<Signal<T>> > disconnectQueue; 
    }; 

    //Can be used as a public SlotRegister member for connecting slots to a private Slot member. 
    //In this way you won't have to write forwarding connect/disconnect boilerplate for your classes. 
    template <typename T> 
    class SlotRegister { 
    public: 
     typedef std::function<T> FunctionType; 
     typedef Signal<T> SignalType; 
     typedef std::shared_ptr<Signal<T>> SharedSignalType; 

     SlotRegister(Slot<T> &a_slot) : 
      slot(a_slot){ 
     } 

     //no protection against duplicates 
     std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){ 
      return slot.connect(a_callback); 
     } 
     //duplicate shared_ptr's will not be added 
     bool connect(std::shared_ptr<Signal<T>> a_value){ 
      return slot.connect(a_value); 
     } 

     void disconnect(std::shared_ptr<Signal<T>> a_value){ 
      slot.disconnect(a_value); 
     } 
    private: 
     Slot<T> &slot; 
    }; 

} 

#endif 

प्रदायक स्कोपगार्ड।HPP:

#ifndef _MV_SCOPEGUARD_H_ 
#define _MV_SCOPEGUARD_H_ 

//Lifted from Alexandrescu's ScopeGuard11 talk. 

namespace MV { 
    template <typename Fun> 
    class ScopeGuard { 
     Fun f_; 
     bool active_; 
    public: 
     ScopeGuard(Fun f) 
      : f_(std::move(f)) 
      , active_(true) { 
     } 
     ~ScopeGuard() { if(active_) f_(); } 
     void dismiss() { active_ = false; } 
     ScopeGuard() = delete; 
     ScopeGuard(const ScopeGuard&) = delete; 
     ScopeGuard& operator=(const ScopeGuard&) = delete; 
     ScopeGuard(ScopeGuard&& rhs) 
      : f_(std::move(rhs.f_)) 
      , active_(rhs.active_) { 
      rhs.dismiss(); 
     } 
    }; 

    template<typename Fun> 
    ScopeGuard<Fun> scopeGuard(Fun f){ 
     return ScopeGuard<Fun>(std::move(f)); 
    } 

    namespace ScopeMacroSupport { 
     enum class ScopeGuardOnExit {}; 
     template <typename Fun> 
     MV::ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) { 
      return MV::ScopeGuard<Fun>(std::forward<Fun>(fn)); 
     } 
    } 

#define SCOPE_EXIT \ 
    auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \ 
    = MV::ScopeMacroSupport::ScopeGuardOnExit() + [&]() 

#define CONCATENATE_IMPL(s1, s2) s1##s2 
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2) 
#ifdef __COUNTER__ 
#define ANONYMOUS_VARIABLE(str) \ 
    CONCATENATE(str, __COUNTER__) 
#else 
#define ANONYMOUS_VARIABLE(str) \ 
    CONCATENATE(str, __LINE__) 
#endif 
} 

#endif 

एक उदाहरण मेरे पुस्तकालय का इस्तेमाल कर रही आवेदन:

#include <iostream> 
#include <string> 
#include "signal.hpp" 

class Observed { 
private: 
    //Note: This is private to ensure not just anyone can spawn a signal 
    MV::Slot<void (int)> onChangeSlot; 
public: 
    typedef MV::Slot<void (int)>::SharedSignalType ChangeEventSignal; 

    //SlotRegister is public, users can hook up signals to onChange with this value. 
    MV::SlotRegister<void (int)> onChange; 

    Observed(): 
     onChange(onChangeSlot){ //Here is where the binding occurs 
    } 

    void change(int newValue){ 
     onChangeSlot(newValue); 
    } 
}; 

class Observer{ 
public: 
    Observer(std::string a_name, Observed &a_observed){ 
     connection = a_observed.onChange.connect([=](int value){ 
      std::cout << a_name << " caught changed value: " << value << std::endl; 
     }); 
    } 
private: 
    Observed::ChangeEventSignal connection; 
}; 

int main(){ 
    Observed observed; 
    Observer observer1("o[1]", observed); 
    { 
     Observer observer2("o[2]", observed); 
     observed.change(1); 
    } 
    observed.change(2); 
} 

के आउटपुट से ऊपर होगा:

o[1] caught changed value: 1 
o[2] caught changed value: 1 
o[1] caught changed value: 2 

आप देख सकते हैं, स्लॉट स्वचालित रूप से मृत संकेत डिस्कनेक्ट ।

2

चूंकि आप कोड मांग रहे हैं, इसलिए मेरी ब्लॉग प्रविष्टि Performance of a C++11 Signal System में सी ++ 11 सुविधाओं के आधार पर पूरी तरह कार्यात्मक सिग्नल सिस्टम का एक एकल-फ़ाइल कार्यान्वयन शामिल है, बिना किसी निर्भरता के (हालांकि एकल-थ्रेडेड, जो एक प्रदर्शन आवश्यकता थी) ।

Signal<void (std::string, int)> sig2; 
sig2() += [] (std::string msg, int d) { /* handler logic */ }; 
sig2.emit ("string arg", 17); 

अधिक उदाहरण इस unit test में पाया जा सकता:

यहाँ एक संक्षिप्त उपयोग उदाहरण है।

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