[英]Variable number of arguments (va_list) with a function callback?
我正在努力實現一個函數,該函數將來會在幾秒鍾內執行另一個函數,具體取決於用戶的輸入。 我有一個類的優先級隊列(我正在調用TimedEvent),它包含一個函數指針,指向我希望它在間隔結束時執行的操作。 比如說用戶希望程序在3秒后調用函數“xyz”,他們會創建一個新的TimedEvent,其時間和函數指針指向xyz並將其添加到優先級隊列(按時間排序,使用最快的事件發生在第一)。
我已經能夠成功獲得優先級隊列以在指定時間后彈出頂部元素,但是我在這里碰到了一堵牆。 我想調用的函數可以采用各種不同的參數,從只有一個整數的函數到帶有3個整數的函數,一個字符串等,並返回不同的值(一些整數,一些字符串等)。 我已經研究了va_lists(我沒有經驗),但這似乎不是答案,除非我遺漏了一些東西。
version): 總之( 版本):
我希望能夠使用相同的函數指針將這些函數稱為“多樣化”:
void func1(int a, int b);<br/>
int func2(int a, string b, OtherClass c);
我是否在使用va_list和函數回調的正確軌道上? 這可以輕松(或根本)實施嗎?
謝謝!
我在這里推斷這些函數是你無法控制的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()。
我認為boost :: bind對你有用。 對於您的應用程序,您可能希望在創建仿函數之前綁定所有參數,然后再將其放入隊列(即,不使用任何_1或_2占位符)。 我認為你不需要像lambda表達式/抽象那樣復雜的東西,但是理解它們是什么是很好的。
DIY方法的+1首席執行官。 這也會奏效,但你必須自己做所有艱苦的工作。
如果您想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_;
};
好吧,有一個真正的硬核技巧利用了這樣一個事實:在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);
}
@Redef,如果你的編譯器將args優化為寄存器,除非它們是vargs,否則它不需要在堆棧上推送它們。 這意味着,在您的第一個示例中,回調函數將期望寄存器中的args,而使用INTFUNC(具有vargs decl)的調用者將它們推送到堆棧上。
結果是回調沒有看到args。
你要做的事幾乎不可能去上班。 您可能需要考慮將參數打包為類似std::vector<boost::any>
。
變量參數列表實際上與您想要的相反。 變量參數列表允許從多個站點調用單個函數,每個站點都有一組唯一的參數。 你想要的是從一個站點調用多個函數,每個函數都有一組唯一的參數 - 而變量參數列表不支持它。
c / invoke是一個允許你在運行時構造任意函數調用的庫,但我認為在這種情況下這樣做太過分了。 聽起來你應該找到一種方法來“規范化”回調函數的簽名,這樣你每次都可以用一個列表,結構,聯合或者允許你通過同一個接口傳遞不同數據的東西來調用它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.