Переменное количество аргументов (va_list) с функцией обратного вызова? - PullRequest
2 голосов
/ 04 декабря 2009

Я работаю над реализацией функции, которая будет выполнять другую функцию через несколько секунд в зависимости от ввода пользователя. У меня есть приоритетная очередь класса (который я называю TimedEvent), который содержит указатель функции на действие, которое я хочу выполнить в конце интервала. Скажем, например, что пользователь хочет, чтобы программа вызывала функцию «xyz» через 3 секунды, он создает новое TimedEvent со временем и указателем функции на xyz и добавляет его в очередь с приоритетами (которая сортируется по времени с скорейшие события происходят первыми).

Мне удалось успешно заставить приоритетную очередь выскочить из верхнего элемента по истечении указанного времени, но здесь я наталкиваюсь на стену. Функции, которые я хочу вызвать, могут принимать различные параметры, от тех, которые принимают только одно целое число, до функций, которые принимают 3 целых числа, строку и т. Д., А также возвращают различные значения (некоторые целые, некоторые строки и т. Д.). Я изучил va_lists (с которыми у меня нет опыта), но, похоже, это не тот ответ, если я что-то упустил.

В итоге (версия TL; DR ):
Я хотел бы иметь возможность вызывать эти функции как "разнородные", как они с тем же указателем функции:

void func1(int a, int b);<br/>
int func2(int a, string b, OtherClass c);

Я на правильном пути с va_list и обратным вызовом функции? Может ли это быть реализовано легко (или вообще)?

Спасибо!

Ответы [ 6 ]

3 голосов
/ 04 декабря 2009

Я думаю boost :: bind будет вам полезен. Для вашего приложения вы, вероятно, захотите связать все аргументы при создании функтора, прежде чем помещать его в очередь (то есть не использовать никакие _1 или _2 заполнители). Я не думаю, что вам нужно что-то настолько сложное, как лямбда-выражения / абстракции , но хорошо бы понять, что это такое.

+ 1 генеральный директор для самостоятельного подхода. Это тоже сработает, но вы должны выполнить всю тяжелую работу самостоятельно.

Если вы хотите создать самоделки, я бы предложил использовать шаблоны вместо определения xfunc и XFuncWrapper для каждой комбинации типов (см. Код ниже).

Кроме того, я думаю, что разрешать разные типы возврата будет бессмысленно - какой бы код ни выводил из очереди и вызывал функции, он будет универсальным. Либо он ожидает одинакового типа возврата от каждой функции, либо он ожидает, что они будут процедурами (return void).

template<typename R>
class FuncWrapper0 : public FuncBase
{
public:
  typedef R (*func)();
  FuncWrapper0(func fp) : fp_(fp) { }
  void operator()() { result_ = fp_(); }
  R getResult() { return result_; }
private:
  func fp_;
  R result_;
};

template<typename R, typename P1>
class FuncWrapper1 : public FuncBase
{
public:
  typedef R (*func)(const P1 &);
  FuncWrapper1(func fp, const P1 &p1) : fp_(fp), p1_(p1) { }
  void operator()() { result_ = fp_(p1_); }
  R getResult() { return result_; }
private:
  func fp_;
  P1 p1_;
  R result_;
};

template<typename R, typename P1, typename P2>
class FuncWrapper2 : public FuncBase
{
public:
  typedef R (*func)(const P1 &, const P2 &);
  FuncWrapper2(func fp, const P1 &p1, const P2 &p2)
    : fp_(fp), p1_(p1), p2_(p2) { }
  void operator()() { result_ = fp_(p1_, p2_); }
  R getResult() { return result_; }
private:
  func fp_;
  P1 p1_;
  P2 p2_;
  R result_;
};
3 голосов
/ 04 декабря 2009

Я предполагаю, что эти функции являются вызовами API, которые вы не можете контролировать. Я взломал то, что, как мне кажется, более или менее соответствует тому, что вы ищете; это довольно грубый шаблон командования.

#include <iostream>
#include <string>

using namespace std;

//these are the various function types you're calling; optional
typedef int (*ifunc)(const int, const int);
typedef string (*sfunc)(const string&);

// these are the API functions you're calling
int func1(const int a, const int b) { return a + b; }
string func2(const string& a) { return a + " world"; }

// your TimedEvent is given one of these
class FuncBase
{
public:
  virtual void operator()() = 0;

};

// define a class like this for each function type
class IFuncWrapper : public FuncBase
{
public:
  IFuncWrapper(ifunc fp, const int a, const int b) 
    : fp_(fp), a_(a), b_(b), result_(0) {}

  void operator()() {
    result_ = fp_(a_, b_);
  }

  int getResult() { return result_; }

private:

  ifunc fp_;
  int a_;
  int b_;
  int result_;

};

class SFuncWrapper : public FuncBase
{
public:
  SFuncWrapper(sfunc fp, const string& a) 
  : fp_(fp), a_(a), result_("") {}

  void operator()() {
    result_ = fp_(a_);
  }

  string getResult() { return result_; }

private:

  sfunc fp_;
  string a_;
  string result_;

};

int main(int argc, char* argv[])
{
  IFuncWrapper ifw(func1, 1, 2);
  FuncBase* ifp = &ifw;

  // pass ifp off to your TimedEvent, which eventually does...
  (*ifp)();
  // and returns.

  int sum = ifw.getResult();
  cout << sum << endl;

  SFuncWrapper sfw(func2, "hello");
  FuncBase* sfp = &sfw;

  // pass sfp off to your TimedEvent, which eventually does...
  (*sfp)();
  // and returns.

  string cat = sfw.getResult();
  cout << cat << endl;

}

Если у вас есть много функций, возвращающих один и тот же тип, вы можете определить подкласс FuncBase, который реализует соответствующий GetResult (), и оболочки для этих функций могут подклассировать его. Конечно, функции, возвращающие void, не требуют GetResult () в своем классе-обертке.

1 голос
/ 14 июня 2012

@ Redef, если ваш компилятор оптимизирует аргументы в регистры, ему не нужно помещать их в стек, если они не являются vargs. Это означает, что в вашем первом примере функция обратного вызова будет ожидать аргументы в регистрах, в то время как вызывающая сторона, использующая INTFUNC (с vargs decl) помещает их в стек.

В результате обратный вызов не видит аргументы.

1 голос
/ 30 апреля 2012

Ну, есть настоящий хардкорный трюк, который использует тот факт, что в C каждая функция является указателем, и вы можете привести указатель к любому другому указателю. Первоначальный код, откуда я это получил, был написан, когда компиляторы не давали ошибок при неявных приведениях, поэтому мне потребовалось некоторое время, чтобы понять, что я должен был привести функции. Что он делает, так это то, что он возвращает функцию обратного вызова в функцию с переменным числом аргументов. Но в то же время функция вызова приводится к функции с 10 аргументами, из которых не все будут предоставлены. Особенно этот последний шаг кажется сложным, но вы видели его раньше, когда вы даете неправильное количество аргументов для printf, и он просто компилируется. Возможно даже, что это то, что va_start / va_end делает под капотом. Код на самом деле предназначен для выполнения пользовательской операции над любым элементом в базе данных, но его можно использовать и в вашей ситуации:

#include    <stdio.h>

typedef int (*INTFUNC)(int,...);
typedef int (*MAPFUNCTION)(int [], INTFUNC, ...);


//------------------CALLBACK FUNCTION----------------

static int  callbackfunction(int DatabaseRecord,int myArgument,int *MyResult){

    if(DatabaseRecord < myArgument){
        printf("mapfunction record:%d<%d -> result %d+%d=%d\n",DatabaseRecord,myArgument,*MyResult,DatabaseRecord,*MyResult+DatabaseRecord);
        *MyResult+=DatabaseRecord;}
    else{
        printf("mapfunction record:%d<%d not true\n",DatabaseRecord,myArgument);
    }
    return 0;   // keep looping
}

//------------------INVOCATION FUNCTION---------------

static int  MapDatabase(int DataBase[], INTFUNC func, void* a1, void* a2, void* a3, void* a4, void* a5, void* a6, void* a7, void* a8, void* a9)
{
int cnt,end;
int ret = 0;

end = DataBase[0]+1;
for(cnt = 1;cnt<end;++cnt){
    if(func(DataBase[cnt], a1, a2, a3, a4, a5, a6, a7, a8, a9)) {
        ret = DataBase[cnt];
        break;
    }

}
return ret;

}

//------------------TEST----------------

void    TestDataBase3(void)
{
    int DataBase[20];
    int cnt;
    int RecordMatch;
    int Result = 0;

    DataBase[0] = 19;
    for(cnt = 1;cnt<20;++cnt){
        DataBase[cnt] = cnt;}

    // here I do the cast to MAPFUNCTION and INTFUNC
    RecordMatch = ((MAPFUNCTION)MapDatabase)(DataBase,(INTFUNC)callbackfunction,11,&Result);
    printf("TestDataBase3 Result=%d\n",Result);

}

Та же функциональность может быть прекрасно написана с помощью va_start / va_end. Это может быть более официальный способ ведения дел, но я считаю его менее удобным для пользователя. Либо функция обратного вызова должна декодировать свои аргументы, либо вам нужно написать блок switch / case внутри функции вызова для каждой комбинации аргументов, которые может иметь функция обратного вызова. Это означает, что вы должны указать формат аргументов (так же, как это делает printf) или вы должны требовать, чтобы все аргументы были одинаковыми, и вы просто указали количество аргументов, но тогда вам все равно придется писать регистр для каждой суммы. аргументов. Вот пример, где функция обратного вызова декодирует аргументы:

#include    <stdio.h>
#include    <stdarg.h>

//------------------CALLBACK FUNCTION----------------

static int  callbackfunction(int DatabaseRecord,va_list vargs)
{
    int myArgument  = va_arg(vargs, int);   // The callbackfunction is responsible for knowing the argument types
    int *MyResult   = va_arg(vargs, int*);

    if(DatabaseRecord < myArgument){
        printf("mapfunction record:%d<%d -> result %d+%d=%d\n",DatabaseRecord,myArgument,*MyResult,DatabaseRecord,*MyResult+DatabaseRecord);
        *MyResult+=DatabaseRecord;}
    else{
        printf("mapfunction record:%d<%d not true\n",DatabaseRecord,myArgument);
    }
    return 0;   // keep looping
}

//------------------INVOCATION FUNCTION---------------

static int  MapDatabase(int DataBase[], int (*func)(int,va_list), int numargs, ...)
{
int     cnt,end;
int     ret = 0;
va_list vargs;


end = DataBase[0]+1;
for(cnt = 1;cnt<end;++cnt){
    va_start( vargs, numargs );     // needs to be called from within the loop, because va_arg can't be reset
    if(func(DataBase[cnt], vargs)) {
        ret = DataBase[cnt];
        break;
    }
    va_end( vargs );                // avoid memory leaks, call va_end
}


return ret;

}

//------------------TEST----------------

void    TestDataBase4(void)
{
    int DataBase[20];
    int cnt;
    int RecordMatch;
    int Result = 0;

    DataBase[0] = 19;
    for(cnt = 1;cnt<20;++cnt){
        DataBase[cnt] = cnt;}


    RecordMatch = MapDatabase(DataBase,callbackfunction,2,11,&Result);
    printf("TestDataBase4a Result=%d\n",Result);
    Result = 0;
    RecordMatch = MapDatabase(DataBase,callbackfunction,0,11,&Result);  // As a hack: It even works if you don't supply the number of arguments.
    printf("TestDataBase4b Result=%d\n",Result);
}
1 голос
/ 04 декабря 2009

c / invoke - это библиотека, которая позволяет вам создавать произвольные вызовы функций во время выполнения, но я думаю, что в этом случае это излишне. Похоже, вы должны найти способ «нормализовать» сигнатуру функции обратного вызова, чтобы вы могли вызывать ее одинаково каждый раз со списком, структурой, объединением или чем-то, что позволяет передавать разные данные через один и тот же интерфейс.

1 голос
/ 04 декабря 2009

То, что вы пытаетесь сделать, почти невозможно заставить работать. Возможно, вы захотите упаковать ваши параметры во что-то вроде std::vector<boost::any>.

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

...