C ++: как передать функцию (не зная ее параметров) другой функции? - PullRequest
9 голосов
/ 29 января 2011

Я пытаюсь создать функцию, которая будет хранить и повторять другую функцию, заданную в качестве параметра в течение определенного периода времени или повторений.Но когда вы хотите передать функцию в качестве параметра, вы должны знать все ее параметры заранее.Как бы я поступил, если бы хотел передать функцию как один параметр, а параметры как другой?

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

Заранее спасибо

Ответы [ 5 ]

15 голосов
/ 29 января 2011

Лучшее, что вы можете сделать, это использовать std::function или boost::function в качестве аргумента вместе с std::bind или boost::bind, чтобы, ну, в общем, связать аргументы с функцией:

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
}

Обратите внимание, что с передачей полностью общего указателя на функцию что-то не так: как вы это используете?Как будет выглядеть тело вызывающей функции для передачи неопределенного числа аргументов неизвестных типов?Это то, что разрешают шаблоны bind, они создают функтор класса, который хранит указатель функции (конкретный указатель на функцию) вместе с копиями аргументов для использования при вызове (обратите внимание на &t в примере, чтобы указатель и необъект скопирован).Результатом bind является функтор, который может вызываться через известный интерфейс, в этом случае он может быть связан внутри function< void() > и вызываться без аргументов.

3 голосов
/ 29 января 2011

Ответ dribeas является верным в том, что касается современного C ++.

Ради интереса, существует также простое технологичное решение из мира C, которое, насколько это возможно, работает в C ++.,Вместо того, чтобы разрешать произвольные параметры, определите функцию как void (*func)(void*) и сделайте «params» void*.Тогда задача вызывающего - определить некоторую структуру, которая будет содержать параметры, и управлять ее жизненным циклом.Обычно вызывающая сторона также записывает простую оболочку для функции, которую действительно необходимо вызвать:

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

На практике вы хотите «запустить и забыть» таймеры, поэтому если вы используете эту схему, вам также может понадобитьсяспособ таймера освободить указатель пользовательских данных после завершения всех срабатываний.

Очевидно, что это ограничивает безопасность типов.В принципе, это не должно иметь значения, потому что тот, кто предоставляет указатель на функцию и указатель на данные пользователя, не должен испытывать особых трудностей при обеспечении их соответствия.Конечно, на практике люди находят способы писать ошибки и обвинять вас, потому что их компилятор не говорил им об ошибках; -)

2 голосов
/ 29 января 2011

Это просто пример того, как вы можете передать указатель на функцию в другую функцию и затем вызвать ее:

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

Аналогично, вы можете написать свой код, если ваша функция принимает другой тип или / и количество параметров!

1 голос
/ 29 января 2011

Если вообще нет никаких правил относительно указателя на функцию, просто используйте void *.

0 голосов
/ 25 февраля 2015

В C ++ 11 все становится действительно просто - вы получаете все, что вам нужно для реализации ваших таймеров.

Наиболее краткий способ передачи вызовов связанных функций - передать функтор, сгенерированный с использованием лямбда-синтаксиса, например: []{ std::cout << "Hello, world!" << std::endl; }. Сгенерированный таким образом объект имеет тип, известный только компилятору, но тип может быть преобразован в 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...