2012-05-23 11 views
12

En C++ 11, ¿cómo declara una función que toma una expresión lambda como argumento? Puedo encontrar muchos recursos en línea para declarar lambdas o tomarlos como parámetros de plantilla, pero lo que realmente me gustaría hacer es poder hacer uso de lambdas como manejadores de devolución de llamada fáciles de declarar, similar a lo que es posible gracias a los cierres en JavaScript y bloques de código en Objective-C.Parámetro/tipo de almacenamiento para C++ 11 lambda

En esencia, el clásico C++ construir quiero reemplazar con una lambda es algo así como:

class MyCallback { 
public: 
    virtual ~MyCallback() {} 
    virtual void operator(int arg) = 0; 
}; 

void registerCallback(const std::shared_ptr<MyCallback> &); 

void foo(void) { 
    int a, b, c; 
    class LocalCallback: public MyCallback { 
     int a, b, c; 
    public: 
     LocalCallback(int a, int b, int c): a(a), b(b), c(c) {} 
     void operator(int arg) { std::cout << (a+b+c)*arg << std::endl; } 
    }; 
    registerCallback(std::shared_ptr<MyCallback>(new LocalCallback(a,b,c))); 
} 

que se simplifica en:

void registerCallback(/* WHAT GOES HERE? */); 

void foo(void) { 
    int a, b, c; 
    registerCallback([=](int arg){std::cout << (a+b+c)*arg << std::endl; }) 
} 

Entonces, ¿qué va a donde he escrito /* WHAT GOES HERE? */ ?

EDITAR: Esto es con el fin de almacenar una devolución de llamada que se devolverá más tarde, en lugar de que se consuma y llame inmediatamente.

+0

Usar tipos locales como argumentos de plantilla no es legal en C++ 03. Algunos compiladores lo permiten como una extensión sin embargo. – bames53

+2

La ventaja de una lambda es que facilita la escritura de devoluciones de llamada. No especificas el código que se usará con lambdas, solo escribes un código que tomará un argumento 'function' y el que llama puede usar (o no) un lambda a su gusto. –

+0

@ barnes53 El código que publiqué no usa tipos locales como un argumento de plantilla. MyCallback es un tipo global, y el tipo local está siendo envuelto en std :: shared_ptr . Utilizo este patrón todo el tiempo, por lo que estoy interesado en la forma en que C++ 11 lo mejora. :) – fluffy

Respuesta

20

Generalmente const std::function<void(int)> & o .

No estoy seguro de cuál es el veredicto sobre si std::function se debe pasar por referencia constante o por valor. Probablemente por valor está bien, especialmente porque vas a copiar de todos modos para almacenar.

En caso de que no esté claro en medio de toda esa sintaxis, void(int) es un tipo de función, y std::function<T> significa aproximadamente, "un functor con la misma firma que las funciones de tipo T".

Lambdas tienen tipos anónimos. No hay manera de nombrar el tipo de su expresión lambda, y los tipos de diferentes expresiones lambda con la misma firma son diferentes:

auto foo = [=](int arg){std::cout << (a+b+c)*arg << std::endl; }; 
auto bar = [=](int arg){std::cout << (a+b+c)*arg << std::endl; }; 
// foo and bar have different types, accessible as decltype(foo), decltype(bar) 

De ahí la necesidad de std::function, que básicamente es un envoltorio tipo de borrado para reunir juntos diferentes funtores con la misma firma en un tipo común. Es el puente entre el polimorfismo estático con plantillas y el polimorfismo dinámico que necesita si desea registrar una devolución de llamada, almacenarla para más adelante y luego llamarla sin haber "recordado" el tipo original.

+0

Gracias. Como es típico, me encontré con la función estándar :: aproximadamente 30 segundos después de publicar mi pregunta, pero tu respuesta es mucho mejor de lo que habría escrito. También lo acepta para el material 'decltype()' y la explicación más completa de cómo funcionan lambdas y tipos de función en C++ 11. (O, lo aceptaré cuando SO me lo permita ...) – fluffy

7
void registerCallback(const std::function<void(int)>& callback); 
5

Considere el uso de una plantilla de función. Hay una gran variedad de buenas razones para, como un mejor comportamiento cuando se sobrecarga (sobrecarga en std::function es doloroso):

template<typename Functor> 
void registerCallback(Functor&& functor); 

(También puede aceptar el parámetro como Functor functor, eso no es demasiado importante.)

Si el código necesita, por ejemplo, almacene el functor más adelante, luego que probablemente se guardará dentro de un std::function. Donde quiere evitar std::function está en parámetros de función.

+0

¿No sería necesario que la implementación se mantenga visible para todo lo que hace uso de ella (es decir, en el archivo de encabezado)? ¿O es C++ 11 finalmente capaz de mantener ocultos los detalles de implementación de los métodos de plantilla? – fluffy

+0

@fluffy: Sí, ese requisito es cierto. Pero ¿eso realmente importa? – ildjarn

+0

@ildjarn Lo hace si no quiero exponer la implementación en un archivo de encabezado público, o si quiero que esto sea un método virtual en una interfaz, sin mencionar todas las otras condiciones de borde con bibliotecas compartidas y otras situaciones donde Los métodos de templado simplemente no funcionan. – fluffy