2015-06-15 11 views
6

Nel 2010 Herb Sutter sosteneva l'uso di oggetti attivi anziché fili nudi in un article su Dr. Dobb's.Valori di ritorno per gli oggetti attivi

Ecco una versione C++ 11:

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

La classe può essere utilizzato in questo modo:

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

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

private: 
    PrivateData somePrivateStateAcrossCalls; 
    Active a; 
}; 

vorrei supportare funzioni membro con i tipi di ritorno non nulle. Ma non riesco a trovare un bel progetto su come implementarlo, cioè senza usare un contenitore che possa contenere oggetti di tipi eterogenei (come boost::any).

Tutte le idee sono benvenute, in particolare le risposte che fanno uso di funzionalità C++ 11 come std::future e std::promise.

+1

Stai chiedendo il (non amato) std :: async? – stefan

+0

Se leggo il riferimento a destra, 'std :: async' avvia nuovi thread o utilizza un pool di thread. Vorrei mettere in coda le attività su un singolo thread per l'esecuzione sequenziale. – enkelwor

risposta

5

Questo richiederà un po 'di lavoro.

Innanzitutto, scrivere task<Sig>. task<Sig> è un std::function che si aspetta che il suo argomento sia mobile, non copiabile.

Il tuo tipo internoMessages sarà task<void()>. Quindi puoi essere pigro e avere il tuo task supporta solo le funzioni null, se lo desideri.

In secondo luogo, invia crea un std::packaged_task<R> package(f);. Quindi ottiene il futuro dall'attività e quindi sposta lo package nella coda dei messaggi. (Questo è il motivo per cui è necessario un solo spostamento std::function, perché packaged_task può essere spostato solo).

Quindi si restituisce il future dal packaged_task.

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

clienti possono afferrare Ahold del std::future e usarlo per (più tardi) ottenere il risultato della chiamata indietro.

In modo divertente, è possibile scrivere un molto semplice mossa-unico compito nullaria come segue:

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

ma che è ridicolmente inefficiente (compito confezionato è roba sincronizzazione in esso), e probabilmente ha bisogno di un po 'di pulizia. Lo trovo divertente perché utilizza uno packaged_task per la parte di spostamento solo std::function.

Personalmente, ho avuto abbastanza ragioni per desiderare attività di spostamento solo (tra questo problema) per ritenere che un solo spostamento std::function valga la pena di essere scritto. Quello che segue è una tale implementazione. Non è fortemente ottimizzato (probabilmente circa veloce quanto più std::function però), e non il debug, ma il design è il suono:

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.

è un primo schizzo in un oggetto task di sola classe di libreria. Utilizza anche alcuni elementi C++ 14 (gli alias std::blah_t) - sostituisci std::enable_if_t<???> con typename std::enable_if<???>::type se sei un compilatore C++ 11-only.

Si noti che il trucco del tipo di ritorno void contiene alcuni trucchi di sovraccarico del modello marginalmente discutibili. (È discutibile se è legale sotto la dicitura dello standard, ma ogni compilatore C++ 11 lo accetterà, ed è probabile che diventi legale se non lo è).