2011-01-29 15 views
9

Próbuję utworzyć funkcję, która będzie przechowywać i powtarzać inną funkcję podaną jako parametr przez określony czas lub powtórzenia. Ale kiedy chcesz przekazać funkcję jako parametr, musisz znać wszystkie jej parametry przed ręką. Co zrobić, jeśli chcę przekazać funkcję jako jeden parametr, a parametry jako kolejne?C++: Jak przekazać funkcję (nie znając jej parametrów) do innej funkcji?

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 

góry dzięki

Odpowiedz

3

Odpowiedź dribeas jest poprawna w odniesieniu do współczesnego C++.

W trosce o interes, istnieje również proste rozwiązanie lo-tech ze świata C, które w miarę możliwości działa w C++. Zamiast zezwalać na dowolne parametry, zdefiniuj funkcję jako void (*func)(void*) i wykonaj "parametry" void*. Wówczas zadaniem rozmówcy jest zdefiniowanie struktury, która będzie zawierać parametry i zarządzanie cyklem życia. Zwykle rozmówca również napisać prosty wrapper do funkcji, która jest naprawdę potrzebne, aby być nazywany:

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

W praktyce chcesz „ognia i zapomnij” liczniki, więc jeśli używasz tego systemu może być konieczne sposób dla timera udaje się uwolnić wskaźnik userdata po zakończeniu wszystkich wypalania.

Oczywiście ma to ograniczone bezpieczeństwo typu. Zasadniczo nie powinno to mieć znaczenia, ponieważ ktokolwiek dostarcza wskaźnik funkcji i wskaźnik danych użytkownika, nie powinien mieć dużej trudności z zapewnieniem ich zgodności. W praktyce ludzie znajdują sposoby na pisanie błędów i sposoby na obwinianie cię, ponieważ ich kompilator nie powiedział im o błędach ;-)

15

Najlepsze, co można zrobić, to użyć std::function lub boost::function jako argumentu razem z std::bind lub boost::bind się dobrze, wiążą argumenty z funkcją:

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 
} 

Należy zauważyć, że coś jest nie tak z przekazaniem w pełni ogólnego wskaźnika funkcji: jak z niego korzystać? Jak wyglądałoby ciało funkcji wywołującej, aby móc przekazać niezdefiniowaną liczbę argumentów nieznanych typów? To właśnie rozwiązują szablony bind, tworzą funktor klasy, który przechowuje wskaźnik funkcji (konkretny wskaźnik funkcji) wraz z kopiami argumentów używanych podczas wywoływania (uwaga na przykład &t, aby wskaźnik, a nie obiekt został skopiowany). Wynik bind jest funktorem, który może być wywołany przez znany interfejs, w tym przypadku może być powiązany wewnątrz function< void() > i wywoływany bez żadnych argumentów.

+0

będzie wywoływał wówczas tylko funkcje nieważne? – Ninja

+0

@Ninja Tak, oczywiście. Jako dzwoniący, jeśli nie wiesz nawet, jaki jest typ zwrotu, w jaki sposób możesz użyć tej funkcji? Możesz użyć 'boost :: function ' i przesłać typ zwrotu do tego, co chcesz. Jeśli chcesz nowoczesną alternatywę C++, możesz użyć 'boost :: function ', aby grać z pewnym typem wymazania. – kizzx2

1

Jeśli w ogóle nie ma reguł dotyczących wskaźnika funkcji, po prostu użyj void *.

+5

'void (*) (void)' jest lepszym "ogólnym" typem wskaźnika funkcji niż 'void *' ponieważ jeśli rzucisz wartość typu 'void (*) (void)' z powrotem do poprawnej funkcji wskaźnik wpisz go jest zagwarantowane, że pierwotna wartość wskaźnika funkcji zostanie zachowana. Nie jest to gwarantowane, jeśli rzucisz wartość wskaźnika funkcji na "void *" i z powrotem. –

+0

@Charles: Gdzie to przeczytałeś w standardzie? –

+3

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

2

To tylko przykład, w jaki sposób można przekazać wskaźnik funkcji do innej funkcji, a następnie wywołać go:

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

Podobnie można napisać kod, jeśli funkcja wykonuje różnego rodzaju i/lub liczby parametrów!

+0

+1, po ponownym przeczytaniu pytania, nie jestem zbyt pewny, że liczba i typy argumentów są nieznane, a jeśli są one znane, to jest to prostsze, lepsze rozwiązanie niż moje. –

+0

Nie o to właściwie pytałem. Wygląda na to, że odpowiedź Davida jest bardziej użyteczna. – Ninja

0

W C++ 11 rzeczy stają się naprawdę proste - dostajesz wszystko, czego potrzebujesz aby zaimplementować liczniki czasu.

Najbardziej zwięzłym sposobem przekazywania powiązanych funkcji jest przekazywanie funktora wygenerowanego za pomocą składni lambda, np .: []{ std::cout << "Hello, world!" << std::endl; }.Wygenerowany w ten sposób obiekt ma typ znany tylko kompilatorowi, ale typ ten można zamienić na 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; 
}