2011-01-29 19 views
9

Estoy tratando de crear una función que almacene y repita otra función dada como un parámetro para una cantidad específica de tiempo o repeticiones dadas. Pero cuando quiera pasar una función como parámetro, debe conocer todos sus parámetros de antemano. ¿Cómo podría hacer si quisiera pasar la función como un parámetro y los parámetros como otro?C++: ¿Cómo paso una función (sin conocer sus parámetros) a otra función?

void AddTimer(float time, int repeats, void (*func), params); // I know params has no type and that (*func) is missing parameters but it is just to show you what I mean 

Gracias de antemano

Respuesta

3

La respuesta de dribeas es correcta en lo que se refiere al C++ moderno.

Por el bien de interés, también hay una solución lo-tech simple del mundo C que, hasta donde llega, funciona en C++. En lugar de permitir parámetros arbitrarios, defina la función como void (*func)(void*), y haga "params" void*. Es entonces el trabajo del que llama para definir una estructura que contendrá los parámetros y gestionar su ciclo de vida. Por lo general, la persona que llama también escribiría una envoltura simple de la función que realmente se necesita para ser llamado:

void myfunc(int, float); // defined elsewhere 

typedef struct { 
    int foo; 
    float bar; 
} myfunc_params; 

void myfunc_wrapper(void *p) { 
    myfunc_params *params = (myfunc_params *)p; 
    myfunc(params->foo, params->bar); 
} 

int main() { 
    myfunc_params x = {1, 2}; 
    AddTimer(23, 5, myfunc_wrapper, &x); 
    sleep(23*5 + 1); 
} 

En la práctica se quiere "dispara y olvida" temporizadores, así que si usa este esquema también se puede necesitar una forma para que el temporizador logre liberar el puntero userdata una vez que todas las activaciones se hayan completado.

Obviamente, esto tiene seguridad de tipo limitado. En principio, no debería importar, ya que quien suministra el puntero a la función y el puntero de datos del usuario no debería tener grandes dificultades para asegurarse de que coincidan. En la práctica, por supuesto, la gente encuentra formas de escribir errores y formas de culparte porque su compilador no les contó sobre los errores ;-)

15

Lo mejor que puede hacer es usar std::function o boost::function como argumento, junto con std::bind o boost::bind que, además, enlazar los argumentos de la función:

void foo() { std::cout << "foo" << std::endl; } 
void bar(int x) { std::cout << "bar(" << x << ")" << std::endl; } 
struct test { 
    void foo() { std::cout << "test::foo" << std::endl; } 
}; 
void call(int times, boost::function< void() > f) 
{ 
    for (int i = 0; i < times; ++i) 
     f(); 
} 
int main() { 
    call(1, &foo);     // no need to bind any argument 
    call(2, boost::bind(&bar, 5)); 
    test t; 
    call(1, boost::bind(&test::foo, &t)); // note the &t 
} 

Tenga en cuenta que hay algo inherentemente incorrecto al pasar un puntero de función completamente genérico: ¿cómo lo usa? ¿Cómo se vería el cuerpo de la función de llamada para poder pasar un número indefinido de argumentos de tipos desconocidos? Eso es lo que resuelven las plantillas bind, crean un funtor de clase que almacena el puntero de función (puntero de función concreta) junto con copias de los argumentos para usar al llamar (observe el &t en el ejemplo para que se copie el puntero y no el objeto) El resultado del bind es un funtor que se puede llamar a través de una interfaz conocida, en este caso puede vincularse dentro de un function< void() > y llamar sin argumentos.

+0

llamará y solo tomará las funciones nulas? – Ninja

+0

@Ninja Sí, por supuesto. Como persona que llama, si ni siquiera sabe cuál es el tipo de devolución, ¿cómo puede usar la función? Puede usar 'boost :: function ' y convertir el tipo de retorno a lo que desee. Si desea la alternativa moderna de C++, puede usar 'boost :: function ' para jugar con algún tipo de borrado. – kizzx2

1

Si realmente no hay reglas sobre el puntero a la función, simplemente use void *.

+5

'void (*) (void)' es mejor tipo de puntero de función "genérico" que 'void *' porque si se arroja un valor de tipo 'void (*) (void)' de regreso al puntero de la función correcta, se escribe se garantiza que se conservará el valor del puntero a la función original. Esto no está garantizado si convierte el valor de un puntero a 'void *' y viceversa. –

+0

@Charles: ¿Dónde lo leíste en el estándar? –

+3

@DanielTrebbien: 5.2.10 [expr.reinterpret.cast]/6. –

2

Es sólo un ejemplo de cómo se podía pasar puntero de función a otra función, y luego llamarlo:

void AddTimer(float time, int repeats, void (*func)(int), int params) 
{ 
    //call the func 
    func(params); 
} 

void myfunction(int param) 
{ 
    //... 
} 

AddTimer(1000.0, 10, myfunction, 10); 

Del mismo modo, se puede escribir el código si su función toma el tipo y/o número de parámetros diferente!

+0

+1, después de volver a leer la pregunta, no estoy muy seguro de que el número y los tipos de argumentos sean desconocidos, y si estos son conocidos, esto es una mejor solución más simple que la mía. –

+0

Esto no es lo que pregunté en realidad. Parece que la respuesta de David es más útil. – Ninja

0

En C++ 11, las cosas se vuelven realmente simples: obtienes todo lo que necesitas para implementar tus temporizadores

La manera más concisa de pasar las llamadas a funciones enlazadas es pasando un functor generado usando la sintaxis lambda, por ejemplo: []{ std::cout << "Hello, world!" << std::endl; }.Un objeto así generado tiene un tipo conocido solo por el compilador, pero el tipo es convertible a std::function<void()>.

#include <functional> 
#include <list> 
#include <chrono> 
#include <thread> 
#include <iostream> 

template <typename Clock = std::chrono::high_resolution_clock> 
class Timers { 
public: 
    using clock = Clock; 
    using duration = typename clock::duration; 
    using time_point = typename clock::time_point; 
private: 
    struct Timer { 
     duration const period; 
     std::function<void()> const call; 
     int repeats; 
     time_point next; 
     Timer(duration $period, int $repeats, std::function<void()> && $call) : 
     period($period), call(std::move($call)), repeats($repeats) {} 
    }; 
    std::list<Timer> m_timers; 
public: 
    Timers() {} 
    Timers(const Timers &) = delete; 
    Timers & operator=(const Timers &) = delete; 
    template <typename C> void add(std::chrono::milliseconds period, 
            int repeats, C && callable) 
    { 
     if (repeats) m_timers.push_back(Timer(period, repeats, callable)); 
    } 
    enum class Missed { Skip, Emit }; 
    void run(Missed missed = Missed::Emit) { 
     for (auto & timer : m_timers) timer.next = clock::now() + timer.period; 
     while (! m_timers.empty()) { 
     auto next = time_point::max(); 
     auto ti = std::begin(m_timers); 
     while (ti != std::end(m_timers)) { 
      while (ti->next <= clock::now()) { 
       ti->call(); 
       if (--ti->repeats <= 0) { 
        ti = m_timers.erase(ti); 
        continue; 
       } 
       do { 
        ti->next += ti->period; 
       } while (missed == Missed::Skip && ti->next <= clock::now()); 
      } 
      next = std::min(next, ti->next); 
      ++ ti; 
     } 
     if (! m_timers.empty()) std::this_thread::sleep_until(next); 
     } 
    } 
}; 

int main(void) 
{ 
    Timers<> timers; 
    using ms = std::chrono::milliseconds; 
    timers.add(ms(1000), 2, []{ std::cout << "Hello, world!" << std::endl; }); 
    timers.add(ms(100), 20, []{ std::cout << "*" << std::endl; }); 
    timers.run(); 
    std::cout << std::endl; 
    return 0; 
} 
Cuestiones relacionadas