2011-12-14 12 views
6

I'm learning boost: asio e C++ 11 contemporaneamente. Uno dei miei programmi di test, che è in realtà un adattamento del one of the samples given in the boost::asio tutorial è il seguente:Come scrivere una funzione anonima/lambda che si passa come una richiamata?

#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 

class printer { 

// Static data members 
private: 
    const static boost::posix_time::seconds one_second; 

// Instance data members 
private: 
    boost::asio::deadline_timer timer; 
    int count; 

// Public members 
public: 
    printer(boost::asio::io_service& io) 
     : timer(io, one_second), count(0) { 

     std::function<void(const boost::system::error_code&)> callback; 
     callback = [&](const boost::system::error_code&) { // critical line 
      if (count < 5) { 
       std::cout << "Current count is " << count++ << std::endl; 

       timer.expires_at(timer.expires_at() + one_second); 
       timer.async_wait(callback); 
      } 
     }; 

     timer.async_wait(callback); 
    } 

    ~printer() { 
     std::cout << "Final count is " << count << std::endl; 
    } 
}; 

const boost::posix_time::seconds printer::one_second(1); 

int main() { 
    boost::asio::io_service io; 
    printer p(io); 
    io.run(); 

    return 0; 
} 

Quando ho eseguito questo programma, ottengo un errore di segmentazione. Capisco perché ottengo il difetto di segmentazione. Dopo che il costruttore è stato eseguito, la variabile callback del costruttore non è inclusa e la variabile callback lambda, che è un riferimento alla variabile callback del costruttore, diventa un riferimento ciondolante.

Così ho modificare la riga critico con:

 callback = [callback, &](const boost::system::error_code&) { // critical line 

quindi compilarlo, eseguirlo, e ottenere un errore di funzione di chiamata di male. Ancora una volta, capisco perché ottengo l'errore di chiamata di funzione cattiva. Nell'ambito di lambda, la variabile callback del costruttore non ha ancora ricevuto alcun valore, quindi per tutti gli scopi pratici è un puntatore a funzione penzolante. Quindi, la variabile callback di lambda, che è una copia della variabile callback del costruttore, è anche un puntatore a funzione pendente.


Dopo aver riflettuto su questo problema per un po ', mi sono reso conto che quello che ho veramente bisogno è che il richiamo sia in grado di riferirsi a se stesso usando un puntatore a funzione, non un riferimento a un puntatore a funzione. L'esempio ha ottenuto questo risultato utilizzando una funzione denominata come callback, anziché anonimo. Tuttavia, il passaggio di funzioni con nome come callback non è molto elegante. C'è un modo per ottenere una funzione anonima avere un puntatore a funzione di se stesso come una variabile locale?

risposta

0

Il lambda sta acquisendo per riferimento la variabile locale "callback", quando viene eseguito il lambda che non sarà valido.

+1

Ho detto molto chiaramente che capisco perché il mio programma non funziona. Voglio sapere _come_ un lambda potrebbe avere un puntatore a funzione di se stesso (che dura per sempre) invece di un riferimento a un puntatore di funzione a se stesso (che potrebbe diventare un riferimento ciondolante una volta che la variabile puntatore di funzione effettiva non rientra nell'ambito). – pyon

+0

OK, pensavo che la soluzione fosse piuttosto chiara quando si riduceva al principio del perché non funziona. Hai provato a rendere "callback" un membro della classe? –

8

ci sono diverse alternative:

  • smettere di usare un lambda. Non devi usarli per tutto, lo sai. Coprono molti casi, ma non coprono tutto. Basta usare un normale vecchio functor.
  • Far memorizzare al lambda un puntatore intelligente a uno std::function assegnato dinamicamente che memorizza il lambda. Per esempio:

    auto pCallback = std::make_shared<std::function<void(const boost::system::error_code&)>>(); 
    auto callback = [=](const boost::system::error_code&) { // critical line 
        if (count < 5) { 
         std::cout << "Current count is " << count++ << std::endl; 
    
         timer.expires_at(timer.expires_at() + one_second); 
         timer.async_wait(pCallback.get()); 
        } 
    }; 
    *pCallback = callback; 
    
+0

L'utilizzo di un functor equivale esattamente all'utilizzo di una funzione con nome. Il nome del functor sarebbe, in sostanza, il vero nome della funzione. // Inoltre, se userò comunque un functor, allora perché non rendere il functor stesso la classe 'printer'? – pyon

+1

@ EduardoLeón: Come ho detto, Lambdas non è per * tutto *. A volte, accetti le limitazioni che hai e lavori al loro interno. Oppure puoi semplicemente fare l'altra cosa che ho detto. Personalmente, il fatto che tu debba usare un metodo di puntatore come questo suggerisce fortemente che dovresti probabilmente usare la più chiara e più ovvia metodologia del functor. Almeno con quello, è ovvio cosa sta succedendo. –

3

per conoscere Asio e C++ 11 vi consiglio il discorso boostcon "Perché C++ 0x è l'Awesomest lingua per Network Programming" dal progettista di ASIO se stesso. (Christopher Kohlhoff)

https://blip.tv/boostcon/why-c-0x-is-the-awesomest-language-for-network-programming-5368225 http://github.com/chriskohlhoff/awesome

In questo discorso, C.K prende una tipica applicazione piccola ASIO e iniziare ad aggiungere C++ 11 caratteristica uno per uno. C'è una parte su lambda nel mezzo del discorso. Il tipo di problema si ha con la vita in tempo di lambda è soluzione utilizzando il seguente schema:

#include <iostream> 
#include <boost/asio.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 
#include <memory> 

class printer 
{ 

// Static data members 
private: 
    const static boost::posix_time::seconds one_second; 

// Instance data members 
private: 
    boost::asio::deadline_timer timer; 
    int count; 

// Public members 
public: 
    printer(boost::asio::io_service& io) 
     : timer(io, one_second), count(0) { 
     wait(); 
    } 

    void wait() { 
     timer.async_wait(
      [&](const boost::system::error_code& ec) { 
       if (!ec && count < 5) { 
       std::cout << "Current count is " << count++ << std::endl; 

       timer.expires_at(timer.expires_at() + one_second); 
       wait(); 
       } 
      }); 
    } 

    ~printer() { 
     std::cout << "Final count is " << count << std::endl; 
    } 
}; 

const boost::posix_time::seconds printer::one_second(1); 

int main() { 
    boost::asio::io_service io; 
    printer p(io); 
    io.run(); 

    return 0; 
} 
+0

Non risponde tecnicamente alla domanda, ma risolve il problema. +1. –

1

solo teoria: si possono fare queste cose con quello che viene chiamato combinatori (come I, S, K).

Prima di utilizzare l'espressione lambda anonima e di tipo F, è possibile definire prima le funzioni come doubleF; (F) -> (F, F) o applyToOneself: (F f) -> F = {return f (f); }.

1

C'è ora una proposta a add a Y-combinator to the C++ standard library (P0200R0) per risolvere esattamente questo problema.

L'idea di base qui è che si passa il lambda a se stesso come prima discussione. Una classe di helper invisibile si occupa della chiamata ricorsiva in background memorizzando la lambda in un membro con nome.

L'applicazione di esempio dalla proposta assomiglia a questo:

#include <functional> 
#include <utility> 

namespace std { 

template<class Fun> 
class y_combinator_result { 
    Fun fun_; 
public: 
    template<class T> 
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {} 

    template<class ...Args> 
    decltype(auto) operator()(Args &&...args) { 
     return fun_(std::ref(*this), std::forward<Args>(args)...); 
    } 
}; 

template<class Fun> 
decltype(auto) y_combinator(Fun &&fun) { 
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun)); 
} 

} // namespace std 

che potrebbe essere utilizzato nel modo seguente per risolvere il problema della domanda:

timer.async_wait(std::y_combinator([](auto self, const boost::system::error_code&) { 
    if (count < 5) { 
     std::cout << "Current count is " << count++ << std::endl; 

     timer.expires_at(timer.expires_at() + one_second); 
     timer.async_wait(self); 
    } 
})); 

Nota l'argomento self che viene passata nel lambda. Questo sarà associato al risultato della chiamata y_combinator, che è un oggetto funzione che è equivalente al lambda con l'argomento self già associato (ad esempio, la sua firma è void(const boost::system::error_code&)).

+0

Utile, ma davvero brutto. Legare manualmente i punti di fissaggio non dovrebbe essere una cosa. – pyon

Problemi correlati