2011-12-14 10 views
6

Estoy aprendiendo boost :: asio y C++ 11 simultáneamente. Uno de mis programas de prueba, que en realidad es una adaptación de one of the samples given in the boost::asio tutorial es la siguiente:¿Cómo escribir una función/lambda anónima que se pase a sí misma como una devolución de llamada?

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

Cuando ejecuto este programa, aparece un fallo de segmentación. Entiendo por qué obtengo la falla de segmentación. Una vez que el constructor termina de ejecutarse, la variable callback del constructor queda fuera del alcance, y la variable callback de lambda, que es una referencia a la variable callback del constructor, se convierte en una referencia colgante.

Así que modificar la línea crítica con:

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

compilarlos a continuación, ejecutarlo, y obtiene un error de llamada a la función malo. De nuevo, entiendo por qué obtengo el error de llamada a la función incorrecta. Dentro del alcance de lambda, la variable callback del constructor aún no tiene ningún valor asignado, por lo que para todos los propósitos prácticos es un puntero de función colgante. Por lo tanto, la variable callback de lambda, que es una copia de la variable callback del constructor, también es un puntero de función colgante.


Después de pensar en este problema por un tiempo, me di cuenta de que lo que realmente necesita es que la devolución de llamada sea capaz de referirse a sí usando un puntero de función, no una referencia a un puntero de función. La muestra logró esto utilizando una función nombrada como devolución de llamada, en lugar de una función anónima. Sin embargo, pasar funciones nombradas como devoluciones de llamada no es muy elegante. ¿Hay alguna manera de obtener una función anónima tener un puntero a la función como una variable local?

Respuesta

0

La lambda está capturando por referencia la variable local "devolución de llamada", cuando se ejecuta la lambda que no será válida.

+1

He declarado muy claramente que entiendo por qué mi programa no funciona. Quiero saber cómo una lambda podría tener un puntero de función a sí mismo (que dura para siempre) en lugar de una referencia a un puntero de función a sí mismo (que podría convertirse en una referencia colgante una vez que la variable del puntero a la función salga del alcance). – pyon

+0

Bien, pensé que la solución era algo clara al desvincularla del principio de por qué no funciona. ¿Has intentado hacer "devolución de llamada" a un miembro de la clase? –

8

hay varias alternativas:

  • dejar de usar un lambda. No tiene que usarlos para todo, ya sabe. Cubren muchos casos, pero no cubren todo. Solo usa un viejo functor normal.
  • Haga que el lambda almacene un puntero inteligente a un std::function dinámicamente asignado que almacene el lambda. Por ejemplo:

    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

Usar un funtor sería exactamente lo mismo que usar una función nombrada. El nombre del functor sería, en esencia, el verdadero nombre de la función. // Además, si voy a usar un functor de todos modos, ¿por qué no hacer que la clase 'printer' sea el mismo functor? – pyon

+1

@ EduardoLeón: Como dije, Lambdas no es para * todo *. A veces, aceptas las limitaciones que tienes y trabajas dentro de ellas. O simplemente puedes hacer otra cosa que dije. Personalmente, el hecho de que tendría que usar un método de puntero como este sugiere fuertemente que probablemente debería utilizar la metodología de functor más clara y más obvia. Al menos con eso, es obvio lo que está pasando. –

3

para aprender acerca de Asio y C++ 11 Recomiendo la charla boostcon "¿Por qué C++ 0x es el idioma de la Red de Awesomes programación" por el diseñador de ASIO sí mismo. (Christopher Kohlhoff)

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

En esta charla, C.K toma una aplicación típica pequeña asio y empezar a añadir función C++ 11, uno por uno. Hay una parte sobre lambda en medio de la charla. El tipo de problema que tenga con tiempo de vida de lambda es solución utilizando el siguiente patrón:

#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

No responde técnicamente la pregunta, pero resuelve el problema. +1. –

1

única teoría: se puede hacer este tipo de cosas, con lo que se llama combinadores (como I, S, K).

Antes de utilizar su expresión lambda anónima e de tipo F, primero puede definir funciones como doubleF; (F) -> (F, F) o applyToOneself: (F f) -> F = {return f (f); }.

1

Ahora existe una propuesta para add a Y-combinator to the C++ standard library (P0200R0) para resolver exactamente este problema.

La idea básica aquí es que pase la lambda a sí mismo como primer argumento. Una clase auxiliar invisible se encarga de la llamada recursiva en segundo plano almacenando el lambda en un miembro designado.

El ejemplo de implementación de la propuesta tiene el siguiente aspecto:

#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 

que podría ser utilizado de la siguiente manera para resolver el problema de la pregunta:

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 del argumento self que se pasa en la lambda. Esto se vinculará con el resultado de la llamada y_combinator, que es un objeto de función que es equivalente a la lambda con el argumento self ya encuadernado (es decir, su firma es void(const boost::system::error_code&)).

+0

Útil, pero realmente feo. La vinculación manual de los puntos de enlace no debería ser una cosa. – pyon

Cuestiones relacionadas