2009-12-03 99 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)將它們壓入堆棧。

結果將是回調沒有看到參數。