2011-01-29 6 views
9

Sto provando a creare una funzione che memorizzerà e ripeterà un'altra funzione assegnata come parametro per una quantità specifica di tempo o ripetizioni fornite. Ma quando vuoi passare una funzione come parametro devi conoscere tutti i suoi parametri prima della mano. Come dovrei fare se volessi passare la funzione come un parametro e i parametri come un altro?C++: come passare una funzione (senza conoscerne i parametri) ad un'altra funzione?

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 

Grazie in anticipo

risposta

3

La risposta di dribeas è corretta per quanto riguarda il C++ moderno.

Per motivi di interesse, c'è anche una semplice soluzione lo-tech del mondo C che, per quanto possibile, funziona in C++. Invece di consentire parametri arbitrari, definire la funzione come void (*func)(void*) e creare "parametri" void*. È quindi compito del chiamante definire una struttura che conterrà i parametri e gestirne il ciclo di vita. In genere il chiamante potrebbe anche scrivere un semplice wrapper per la funzione che è davvero bisogno di essere chiamato:

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

In pratica si vuole "spara e dimentica" timer, quindi se si utilizza questo schema si può anche bisogno di un modo perché il timer riesca a liberare il puntatore userdata una volta completati tutti gli spari.

Ovviamente questo ha limitato la sicurezza del tipo. In linea di principio non dovrebbe essere importante, perché chiunque fornisca il puntatore della funzione e il puntatore dei dati dell'utente non dovrebbe avere molte difficoltà a garantire che corrispondano. In pratica, naturalmente, le persone trovano modi per scrivere bug e modi per biasimarti perché il loro compilatore non ha detto loro dei bug ;-)

15

Il meglio che si può fare è utilizzare std::function o boost::function come argomento, insieme a std::bind o boost::bind, beh, legano gli argomenti con la funzione:

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 
} 

Nota che c'è qualcosa di intrinsecamente sbagliato nel passare un puntatore a funzione generica: come lo usi? Come apparirebbe il corpo della funzione chiamante in grado di passare un numero indefinito di argomenti di tipi sconosciuti? Questo è ciò che risolvono i modelli bind, creano un functor di classe che memorizza il puntatore di funzione (puntatore a funzione concreta) insieme alle copie degli argomenti da usare durante la chiamata (notare il &t nell'esempio in modo che il puntatore e non l'oggetto sia copiato). Il risultato di bind è un functor che può essere chiamato attraverso un'interfaccia conosciuta, in questo caso può essere associato a function< void() > e richiamato senza argomenti.

+0

chiamerà quindi solo prendere funzioni void? – Ninja

+0

@Ninja Sì, certo. Come chiamante, se non sai nemmeno quale sia il tipo di ritorno, come puoi usare la funzione? Puoi usare la funzione 'boost :: ' e trasmettere il tipo restituito a ciò che vuoi. Se si desidera un'alternativa C++ moderna, è possibile utilizzare la funzione 'boost :: ' per giocare con alcuni tipi di cancellazione. – kizzx2

1

Se non esistono regole sul puntatore di funzione, utilizzare void *.

+5

'void (*) (void)' è un tipo di puntatore di funzione "generico" migliore di 'void *' perché se si esegue il cast di un valore di tipo 'void (*) (void)' il puntatore della funzione corretta digita è garantito che il valore del puntatore della funzione originale verrà mantenuto. Questo non è garantito se lanci un valore del puntatore di funzione a 'void *' e back. –

+0

@Charles: dove l'hai letto nello standard? –

+3

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

2

E 'solo un esempio di come si potrebbe passare puntatore a funzione ad un'altra funzione, e poi lo chiamano:

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); 

Allo stesso modo, è possibile scrivere il codice se la funzione prende tipo e/o il numero di parametri diverso!

+0

+1, dopo aver riletto di nuovo la domanda, non sono troppo sicuro che il numero e il tipo di argomenti siano sconosciuti, e se questi sono noti questo è una soluzione più semplice della mia. –

+0

Questo non è quello che ho chiesto in realtà. Sembra che la risposta di David sia più utile. – Ninja

0

In C++ 11, le cose diventano molto semplici - ottieni tutto ciò di cui hai bisogno per implementare i tuoi timer.

Il modo più conciso per passare chiamate di funzioni associate è il passaggio di un funtore generato utilizzando la sintassi lambda, ad esempio []{ std::cout << "Hello, world!" << std::endl; }.Un oggetto così generato ha un tipo noto solo al compilatore, ma il tipo è convertibile in 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; 
}