2

我正在努力实现一个功能,该功能将在几秒钟后执行另一个功能,具体取决于用户的输入。我有一个类的优先级队列(我称之为 TimedEvent),其中包含一个函数指针,指向我希望它在间隔结束时执行的操作。例如,假设用户希望程序在 3 秒后调用函数“xyz”,他们将使用时间和指向 xyz 的函数指针创建一个新的 TimedEvent,并将其添加到优先级队列(按时间排序,使用最先发生的事件)。

我已经能够成功地让优先级队列在指定时间后弹出顶部元素,但在这里遇到了障碍。我要调用的函数可以采用各种不同的参数,从只采用单个整数的参数到采用 3 个整数、一个字符串等的参数,并且还返回不同的值(一些整数、一些字符串等)。我研究了 va_lists (我没有经验),但这似乎不是答案,除非我遗漏了一些东西。

总而言之(TL;DR版本):
我希望能够将这些函数称为“多样化”,因为它们具有相同的函数指针:

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

我是否在正确的轨道上使用 va_list 和函数回调?这可以轻松(或根本)实现吗?

谢谢!

4

6 回答 6

3

我在这里推断这些函数是您无法控制的 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()。

于 2009-12-04T05:10:10.430 回答
3

我认为boost::bind对你有用。对于您的应用程序,您可能希望在创建仿函数时绑定所有参数,然后再将其放入队列(即,不使用任何 _1 或 _2 占位符)。我认为您不需要像lambda 表达式/抽象那样复杂的东西,但了解它们是什么是件好事。

DIY 方法的 +1 首席执行官。这也可以,但是您必须自己完成所有艰苦的工作。

但是,如果您想 DIY,我建议您使用模板而不是为每种类型组合定义 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_;
};
于 2009-12-04T08:02:41.397 回答
1

您尝试做的事情几乎不可能开始工作。您可能需要考虑将参数打包成类似的东西std::vector<boost::any>

可变参数列表实际上与您想要的相反。可变参数列表允许从多个站点调用单个函数,每个站点都有一组唯一的参数。您想要的是从单个站点调用多个函数,每个函数都有一组唯一的参数——而可变参数列表不支持这一点。

于 2009-12-03T23:57:07.587 回答
1

c/invoke是一个库,可让您在运行时构造任意函数调用,但我认为在这种情况下这太过分了。听起来您应该找到一种方法来“规范化”回调函数的签名,以便您每次都可以使用列表、结构、联合或允许您通过同一接口传递不同数据的东西以相同的方式调用它。

于 2009-12-04T00:12:02.797 回答
1

嗯,有一个真正的硬核技巧利用了这样一个事实,即在 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);
}
于 2012-04-30T09:25:59.393 回答
1

@Redef,如果您的编译器将 args 优化为寄存器,则除非它们是 vargs,否则它不需要将它们推入堆栈。这意味着,在您的第一个示例中,回调函数将在寄存器中期待 args,而使用 INTFUNC(带有 vargs decl)的调用者将它们推入堆栈。

结果将是回调看不到参数。

于 2012-06-14T06:27:29.093 回答