2015-10-30 2 views
8

Desidero creare una macchina a stati che lavori elabora i segnali inviati nella propria thread. Uso Visual Studio 2015, quindi C++ 11 e parzialmente C++ 14 sono supportati. I segnali sono memorizzati in contenitori. Ogni segnale è rappresentato come una funzione std ::. Vorrei aspettare dal client fino a quando la macchina di stato elabora il segnale che è stato inviato, quindi è una sorta di segnale sincrono.Cattura std :: promise in a lambda C++ 14

Il mio problema è: non riesco a catturare uno std :: promise in un lambda e aggiungerlo al contenitore.

#include <functional> 
#include <future> 
#include <list> 

std::list<std::function<int()>> callbacks; 

void addToCallbacks(std::function<int()>&& callback) 
{ 
    callbacks.push_back(std::move(callback)); 
} 

int main() 
{ 
    std::promise<int> prom; 
    auto fut = prom.get_future(); 

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value 
    auto callback = [proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; }; 

    // This does not compile 
    addToCallbacks(std::move(callback)); 

    // This does not compile either, however this lambda is a temporal value (lvalue) 
    addToCallbacks([proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; }); 

    return 0; 
} 

Quali sono le soluzioni, se

  • voglio evitare la cattura la promessa con riferimento
  • voglio evitare la cattura un puntatore * o uno shared_ptr alla promessa

Sarebbe bello inserire la promessa nella classe in qualche modo che il lambda genera. Ciò significa che la lambda non è più copiabile, solo mobile. È possibile a tutti?

+0

Design: non usare 'std :: list' meno che davvero, davvero bisogno di stabilità iteratore. Il tuo obiettivo è avere il "valore di ritorno" entrambi visualizzati in futuro (che è disgiunto da qualche parte), e nel valore di ritorno della funzione che chiami? – Yakk

risposta

10

std::function possono essere creati solo da funtori che sono copiabili. Da [func.wrap.func.con]:

template<class F> function(F f); 
template <class F, class A> function(allocator_arg_t, const A& a, F f); 

Richiede: F sarà CopyConstructible.

std::promise è non copiabile, quindi non c'è modo di attaccare un funtore con questo utente in un std::function. Periodo.

Dato che vuoi che il tuo funtore si assuma effettivamente la promessa, questo non ti lascia molte opzioni. Praticamente std::shared_ptr<std::promise>. Qualsiasi altra opzione non funziona (ad esempio std::unique_ptr<std::promise>), ti lascia con un oggetto sospeso (ad esempio std::reference_wrapper<std::promise>) o ti lascia problemi di gestione della memoria (ad esempio std::promise*).


È tuttavia possibile utilizzare un valore diverso da std::function. Puoi dare un'occhiata all'idea task di Yakk here, così come allo dypfunction_mohere, che creano entrambi aromi mobili di std::function.

+1

Oh, dyp's 'function_mo' è * rude * - usa' std :: function', ma memorizza un 'hack ' che contiene un 'F' e finge di essere copiato, ma dette copie semplicemente buttano (penso che siano dovrebbe 'std :: terminate' o semplicemente non fare nulla in pratica, in modo che possano dichiarare che non gettare). Fondamentalmente, giace su 'std :: function', quindi espone solo le parti di' std :: function' che dovrebbero funzionare. Finché si può prendere in giro una garanzia dallo standard che le operazioni di copia saranno chiamate solo quando si copia la 'std :: function', è sicuro, ma ciò richiederebbe un delving standard ... – Yakk

+0

Ah; richiede anche che il 'F' sia costruibile come' F() ', come' hack (hack const &) 'chiama' f() 'implicitamente. - tangenzialmente, 'std :: promise ' può essere usato come titolare di 'std :: promise ' che ignora il suo valore di ritorno. Oppure l'OP potrebbe semplicemente memorizzare 'std :: promise ' s (non si sa cosa sta facendo l'OP con i valori di ritorno?) – Yakk

2

È semplice eseguire il rollover della propria classe di funzione polimorfica. Questo esempio corregge l'argomento e restituisce i tipi, ma un po 'più di lavoro potrebbe templatizzarli se lo si desidera.

#include <iostream> 
#include <functional> 
#include <future> 
#include <list> 

// declare a non-polymorphic container for any function object that takes zero args and returns an int 
// in addition, the contained function need not be copyable 
class move_only_function 
{ 
    // define the concept of being callable while mutable 
    struct concept 
    { 
     concept() = default; 
     concept(concept&&) = default; 
     concept& operator=(concept&&) = default; 
     concept(const concept&) = delete; 
     concept& operator=(const concept&) = default; 
     virtual ~concept() = default; 

     virtual int call() = 0; 
    }; 

    // model the concept for any given function object 
    template<class F> 
    struct model : concept 
    { 
     model(F&& f) 
     : _f(std::move(f)) 
     {} 

     int call() override 
     { 
      return _f(); 
     } 

     F _f; 
    }; 

public: 
    // provide a public interface 
    int operator()() // note: not const 
    { 
     return _ptr->call(); 
    } 

    // provide a constructor taking any appropriate object 
    template<class FI> 
    move_only_function(FI&& f) 
    : _ptr(std::make_unique<model<FI>>(std::move(f))) 
    {} 

private: 
    std::unique_ptr<concept> _ptr; 
}; 

std::list<move_only_function> callbacks; 

void addToCallbacks(move_only_function&& callback) 
{ 
    callbacks.push_back(std::move(callback)); 
} 

int main() 
{ 
    std::promise<int> prom; 
    auto fut = prom.get_future(); 

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value 
    auto callback = [proms=std::move(prom)]() mutable { proms.set_value(5); return 5; }; 

    // This now compiles 
    addToCallbacks(std::move(callback)); 

    std::promise<int> prom2; 
    auto fut2 = prom2.get_future(); 

    // this also compiles 
    addToCallbacks([proms = std::move(prom2)]() mutable { proms.set_value(6); return 6; }); 

    for (auto& f : callbacks) 
    { 
     std::cout << "call returns " << f() << std::endl; 
    } 

    std::cout << "fut = " << fut.get() << std::endl; 
    std::cout << "fut2 = " << fut2.get() << std::endl; 

    return 0; 
} 

risultato atteso: recensione

call returns 5 
call returns 6 
fut = 5 
fut2 = 6 
+0

Hai fornito una soluzione completa. Questo è carino. Tuttavia mi piacerebbe attaccare di più al core C++ e non introdurre tali varianti speciali di un funtore. Penso che userò un puntatore intelligente che punta alla promessa e la catturerò in base al valore. Comunque, grazie per i tuoi sforzi! Ti ho dato +1 per questo post :-) – user2281723