2009-12-03 35 views
2

根据用户的输入,我正在实现一个将在未来几秒钟内执行另一个函数的函数。我有一个类(我称为TimedEvent)的优先级队列,它包含一个指向我希望它在间隔结束时执行的动作的函数指针。举例来说,用户希望程序在3秒后调用函数“xyz”,他们将创建一个新的TimedEvent,其时间和函数指针指向xyz,并将其添加到优先级队列中(按时间排序,最快的事件首先发生)。带有函数回调的变量数量(va_list)?

我已经能够成功获得优先级队列,在指定时间之后弹出顶部元素,但在此处跑到墙上。我想要调用的函数可以采用各种不同的参数,从只有一个整数的那些参数到带有三个整数,一个字符串等的参数,还返回不同的值(某些整数,某些字符串等)。我研究了va_lists(我没有经验),但这似乎不是答案,除非我错过了某些东西。

综上所述(所述 TL; DR版本):
我想能够调用这些函数为 “不同” 作为这些具有相同功能的指针:

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

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

谢谢!

+2

如果您使用不同数量的参数调用函数,以至于您事先不知道这些函数,那么您会在哪些位置将值作为参数传递给调用,以便调用回调? – 2009-12-03 23:49:53

+0

您需要lambdas,您的编译器是否支持它们?如果没有,看看Boost的凤凰懒惰功能。 – 2009-12-04 00:07:38

+0

您如何首先获取参数? – 2009-12-04 00:13:06

回答

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()。

+0

这完全是我在找的东西...感谢一堆! – 2009-12-04 10:24:37

1

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

可变参数列表与您想要的完全相反。可变参数列表允许从多个站点调用单个功能,每个站点都有一组唯一的参数。你想要的是从一个站点调用多个函数,每个函数都有一组独特的参数 - 而一个可变参数列表并不支持。

1

c/invoke是一个库,可以让你在运行时构造任意函数调用,但我认为这在这种情况下是矫枉过正。这听起来像是你应该找到一种方法来“回归”回调函数的签名,这样你就可以每次使用列表,结构,联合或者其他方法来调用它,这样你就可以通过相同的接口传递不同的数据。

3

我认为boost::bind将对您有用。对于您的应用程序,您可能希望在创建函数时将所有参数绑定到队列中(即,不使用_1或_2占位符)。我不认为你需要像lambda expressions/abstractions那样复杂的东西,但很好理解它们是什么。

+1首席执行官的DIY方法。这也会起作用,但你必须自己做所有的努力。

如果你想DIY,但我会建议使用模板,而不是为每种类型的组合定义一个xfunc和XFuncWrapper(见下面的代码)。

此外,我认为允许不同的返回类型将毫无意义 - 无论代码是否在排队并调用函数都将是通用的。要么它期望来自每个函数的相同类型的返回,要么期望它们是过程(返回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_; 
}; 
+0

API调用结果没有太多的返回类型和参数组合,所以我最终使用了ceo的方法。不过,我可以肯定地看到使用这种方法的优势,对于更大范围的项目......谢谢! – 2009-12-04 10:27:58

+0

不错的工作。我正在试图制定一个模板化的版本,但是这一点迟了一点。 – ceo 2009-12-04 13:51:00

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用来写入。这可能是更正式的做事方式,但我觉得它不太方便用户。要么回调函数需要解码其参数,要么需要在调用函数中为回调函数可能具有的每个参数组合写入开关/大小写块。这意味着你必须提供参数的格式(就像printf一样),或者你必须要求所有的参数都是相同的,你只需要提供参数的数量,但是你仍然必须为每个数量写一个case的论据。这里是回调函数解码参数为例:

#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

@Redef,如果你的编译器优化ARGS到寄存器,它不需要把他们在栈上,除非他们是vargs。这意味着,在您的第一个示例中,该回调函数将期待寄存器中的参数,而使用INTFUNC的调用方(使用vargs decl)将它们压入堆栈。

结果将是回调没有看到参数。