2015-06-15 7 views
6

2010 में वापस, हर्ब सटर ने डॉ। डोबब पर article में नग्न धागे की बजाय सक्रिय वस्तुओं के उपयोग की वकालत की।सक्रिय वस्तुओं के लिए वापसी मूल्य

class Active { 
public: 
    typedef std::function<void()> Message; 

    Active(const Active&) = delete; 
    void operator=(const Active&) = delete; 

    Active() : done(false) { 
     thd = std::unique_ptr<std::thread>(new std::thread([=]{ this->run(); })); 
    } 

    ~Active() { 
     send([&]{ done = true; }); 
     thd->join(); 
    } 

    void send(Message m) { mq.push_back(m); } 

private: 
    bool done; 
    message_queue<Message> mq; // a thread-safe concurrent queue 
    std::unique_ptr<std::thread> thd; 

    void run() { 
     while (!done) { 
      Message msg = mq.pop_front(); 
      msg(); // execute message 
     } // note: last message sets done to true 
    } 
}; 

वर्ग इस तरह इस्तेमाल किया जा सकता:

class Backgrounder { 
public: 
    void save(std::string filename) { a.send([=] { 
     // ... 
    }); } 

    void print(Data& data) { a.send([=, &data] { 
     // ... 
    }); } 

private: 
    PrivateData somePrivateStateAcrossCalls; 
    Active a; 
}; 

मैं गैर शून्य वापसी प्रकार के साथ सदस्य कार्य का समर्थन चाहते हैं

यहाँ एक सी ++ 11 संस्करण है। लेकिन मैं इसे लागू करने के लिए एक अच्छे डिजाइन के साथ नहीं आ सकता, यानी एक कंटेनर का उपयोग किए बिना जो विषम प्रकारों की वस्तुओं को पकड़ सकता है (जैसे boost::any)।

किसी भी विचार का स्वागत है, विशेष रूप से उत्तर जो सी ++ 11 सुविधाओं जैसे std::future और std::promise का उपयोग करते हैं।

+1

क्या आप (अनदेखा) std :: async के लिए पूछ रहे हैं? – stefan

+0

यदि मैं संदर्भ सही पढ़ता हूं, तो 'std :: async' नए धागे लॉन्च करता है या थ्रेड पूल का उपयोग करता है। मैं क्रमिक निष्पादन के लिए एक थ्रेड पर कार्यों को कतार करना चाहता हूं। – enkelwor

उत्तर

5

यह कुछ काम करेगा।

सबसे पहले, task<Sig> लिखें। task<Sig> एक std::function है जो केवल इसकी तर्क चलने योग्य, कॉपी करने योग्य नहीं है।

आपका आंतरिक प्रकारMessagestask<void()> होने जा रहे हैं। तो आप आलसी हो सकते हैं और यदि आप चाहें तो task केवल न्यूलरी फ़ंक्शंस का समर्थन करें।

दूसरा, भेजें std::packaged_task<R> package(f); बनाता है। इसके बाद भविष्य को कार्य से बाहर कर दिया जाता है, और फिर संदेशों की अपनी कतार में package चलाता है। (यही कारण है कि आपको केवल std::function की आवश्यकता है, क्योंकि packaged_task केवल स्थानांतरित किया जा सकता है)।

फिर आप packaged_task से future वापस लौटें।

template<class F, class R=std::result_of_t<F const&()>> 
std::future<R> send(F&& f) { 
    packaged_task<R> package(std::forward<F>(f)); 
    auto ret = package.get_future(); 
    mq.push_back(std::move(package)); 
    return ret; 
} 

ग्राहकों std::future के ahold हड़पने और कॉल के पीछे परिणाम (बाद में) प्राप्त करने के लिए उपयोग कर सकते हैं।

template<class R> 
struct task { 
    std::packaged_task<R> state; 
    template<class F> 
    task(F&& f):state(std::forward<F>(f)) {} 
    R operator()() const { 
    auto fut = state.get_future(); 
    state(); 
    return f.get(); 
    } 
}; 

लेकिन यह है कि (पैक काम उस में तुल्यकालन सामान है) हास्यास्पद अक्षम है, और शायद कुछ सफाई की जरूरत:

Amusingly, आप एक बहुत सरल कदम केवल-nullary कार्य इस प्रकार लिख सकते हैं। मुझे यह मनोरंजक लगता है क्योंकि यह केवल का उपयोग केवल std::function भाग के लिए करता है।

व्यक्तिगत रूप से, मैंने केवल-केवल कार्य (इस समस्या के बीच) को महसूस करने के पर्याप्त कारणों में भाग लिया है ताकि यह महसूस किया जा सके कि केवल std::function लेखन योग्य है। इस तरह के एक कार्यान्वयन का पालन करता है। यह भारी अनुकूलित है (शायद के रूप में तेजी से के बारे में अधिकांश के रूप में std::function हालांकि), और डिबग नहीं, लेकिन डिजाइन आवाज़ है:

template<class Sig> 
struct task; 
namespace details_task { 
    template<class Sig> 
    struct ipimpl; 
    template<class R, class...Args> 
    struct ipimpl<R(Args...)> { 
    virtual ~ipimpl() {} 
    virtual R invoke(Args&&...args) const = 0; 
    }; 
    template<class Sig, class F> 
    struct pimpl; 
    template<class R, class...Args, class F> 
    struct pimpl<R(Args...), F>:ipimpl<R(Args...)> { 
    F f; 
    R invoke(Args&&...args) const final override { 
     return f(std::forward<Args>(args)...); 
    }; 
    }; 
    // void case, we don't care about what f returns: 
    template<class...Args, class F> 
    struct pimpl<void(Args...), F>:ipimpl<void(Args...)> { 
    F f; 
    template<class Fin> 
    pimpl(Fin&&fin):f(std::forward<Fin>(fin)){} 
    void invoke(Args&&...args) const final override { 
     f(std::forward<Args>(args)...); 
    }; 
    }; 
} 
template<class R, class...Args> 
struct task<R(Args...)> { 
    std::unique_ptr< details_task::ipimpl<R(Args...)> > pimpl; 
    task(task&&)=default; 
    task&operator=(task&&)=default; 
    task()=default; 
    explicit operator bool() const { return static_cast<bool>(pimpl); } 

    R operator()(Args...args) const { 
    return pimpl->invoke(std::forward<Args>(args)...); 
    } 
    // if we can be called with the signature, use this: 
    template<class F, class=std::enable_if_t< 
    std::is_convertible<std::result_of_t<F const&(Args...)>,R>{} 
    >> 
    task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {} 

    // the case where we are a void return type, we don't 
    // care what the return type of F is, just that we can call it: 
    template<class F, class R2=R, class=std::result_of_t<F const&(Args...)>, 
    class=std::enable_if_t<std::is_same<R2, void>{}> 
    > 
    task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {} 

    // this helps with overload resolution in some cases: 
    task(R(*pf)(Args...)):task(pf, std::true_type{}) {} 
    // = nullptr support: 
    task(std::nullptr_t):task() {} 

private: 
    // build a pimpl from F. All ctors get here, or to task() eventually: 
    template<class F> 
    task(F&& f, std::false_type /* needs a test? No! */): 
    pimpl(new details_task::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) }) 
    {} 
    // cast incoming to bool, if it works, construct, otherwise 
    // we should be empty: 
    // move-constructs, because we need to run-time dispatch between two ctors. 
    // if we pass the test, dispatch to task(?, false_type) (no test needed) 
    // if we fail the test, dispatch to task() (empty task). 
    template<class F> 
    task(F&& f, std::true_type /* needs a test? Yes! */): 
    task(f?task(std::forward<F>(f), std::false_type{}):task()) 
    {} 
}; 

live example

लाइब्रेरी-क्लास मूव-केवल कार्य ऑब्जेक्ट में पहला स्केच है। यह कुछ सी ++ 14 सामान (std::blah_t उपनाम) का भी उपयोग करता है - typename std::enable_if<???>::type के साथ प्रतिस्थापित करें यदि आप सी ++ 11-केवल कंपाइलर हैं।

ध्यान दें कि void रिटर्न टाइप चाल में कुछ मामूली संदिग्ध टेम्पलेट अधिभार चाल शामिल हैं। (यह तर्कसंगत है कि यह मानक के शब्दों के तहत कानूनी है, लेकिन प्रत्येक सी ++ 11 कंपाइलर इसे स्वीकार करेगा, और यदि यह नहीं है तो यह कानूनी बनने की संभावना है)।

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