2015-06-15 20 views
6

2010 yılında, Herb Sutter, Dr. Dobb's üzerinde bulunan article numaralı telefondaki çıplak iş parçacıklarının yerine aktif nesnelerin kullanımını savundu.Aktif nesneler için döndürülen değerler

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 
    } 
}; 

sınıf bu gibi kullanılabilir: Ben olmayan void dönüş türleri ile üye işlevlerini desteklemek istiyoruz

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

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

private: 
    PrivateData somePrivateStateAcrossCalls; 
    Active a; 
}; 

İşte

bir C++ 11 versiyonudur. Ama bunu nasıl uygulayacağımı, yani heterojen tipteki nesneleri (boost::any gibi) tutabilen bir konteyner kullanmadan güzel bir tasarım yapamıyorum.

Herhangi bir fikir, özellikle std::future ve std::promise gibi C++ 11 özelliklerinden yararlanan yanıtlar açıktır.

+1

(Sevilmemiş) std :: async için mi çalışıyorsunuz? – stefan

+0

Referans hakkını okursam, 'std :: async' yeni konuları başlatır veya bir iş parçacığı havuzu kullanır. Sıralı yürütme için görevleri tek bir iş parçacığında sıraya almak istiyorum. – enkelwor

cevap

5

Bu işlem biraz zaman alacaktır.

İlk olarak task<Sig> yazın. task<Sig>, bağımsız değişkeninin yalnızca taşınabilir, kopyalanamaz olması beklenen bir std::function olduğunu.

iç tipMessages ürününüz task<void()> olacaktır. Böylece tembel olabilir ve task numaranızı sadece isterseniz nullary işlevlerini destekleyebilirsiniz.

İkincisi, bir std::packaged_task<R> package(f); oluşturur. Daha sonra geleceği görevin dışına çıkarır ve ardından package mesajlarınızın sırasına taşır. (Bu nedenle, yalnızca std::function numaralı bir araca ihtiyacınız vardır, çünkü packaged_task yalnızca taşınabilir).

futurepackaged_task'dan döndürün.

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; 
} 

istemcileri std::future sıkıca tutar ve geri aramanın (daha sonra) olsun sonuç için kullanabilirsiniz.

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(); 
    } 
}; 

ama bu (ambalajlı görev içinde senkronizasyon şeyler vardır) gülünç verimsiz olduğunu ve muhtemelen bazı temizlenmesi gerekiyor şu şekildedir:

Eğlenceli, sen gerçekten basit bir hareket okunur nullary görevi yazabilir. Bunu eğlenceli buluyorum, çünkü yalnızca std::function bölümü için packaged_task kullanıyor.

Şahsen, yalnızca taşıma görevlerinin (bu sorun arasında) yalnızca bir harekete geçirme std::function yazılmaya değer olduğunu hissetmek için yeterli nedenlerle karşılaşıyorum. Ne böyle bir uygulama. Bu ağır (muhtemelen yaklaşık kadar hızlı ancak çoğu olarak std::function) optimize edilmemiş ve ayıklanan değil ama tasarım sesidir edilir:

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.

, kitaplık sınıfı yalnızca taşıma görev nesnesindeki ilk çizimdir. Ayrıca bazı C++ 14 şeyler (std::blah_t takma adlar) kullanır -yerine, C++ 11 yalnızca bir derleyici iseniz, typename std::enable_if<???>::type ile değiştirin.

void dönüş türü numarasının bazı marjinal olarak şüphelenilen şablon aşırı yükleme numaralarını içerdiğini unutmayın. (Bu standartın ifadesi altında yasal olup olmadığı tartışılabilir, ancak her C++ 11 derleyici bunu kabul edecek ve eğer değilse yasal hale gelecektir).