2009-12-03 3 views
2

나는 사용자의 입력에 따라 앞으로 몇 초 안에 다른 함수를 실행할 함수를 구현하고있다. 나는 클래스의 우선 순위 큐 (나는 TimedEvent를 호출한다)에 인터벌의 끝에 실행할 액션에 대한 함수 포인터를 포함하고있다. 예를 들어, 사용자가 3 초 후에 프로그램이 "xyz"함수를 호출하기를 원하면, 시간과 함수 포인터가있는 xyz를 새 TimedEvent로 생성하고 우선 순위 큐에 추가합니다 (시간순으로 정렬 됨). 가장 빠른 사건이 처음 일어난다).함수 콜백이있는 변수 개수 (va_list)?

우선 순위 큐를 성공적으로 가져 와서 지정한 시간 후에 최상위 요소를 튀어 오르게 할 수 있었지만 여기서 벽으로 실행 중입니다. 호출하고자하는 함수는 하나의 정수 만 취하는 함수에서 3 개의 정수, 문자열 등을 취하는 함수와 다른 값 (일부 int, 일부 문자열 등)을 반환하는 등 다양한 다양한 매개 변수를 사용할 수 있습니다. 나는 va_lists (나는 경험이 없다)를 들여다 보았다. 그러나 이것은 뭔가 빠진 것이 아니라면 대답이 아닌 것 같다. 요약

합니다 ( TL; DR 버전) :

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

:
나는 "다양한"이러한 함수를 호출 할 수 있도록하고 싶습니다 같은 함수 포인터와 이들과 같은 va_list 및 함수 콜백으로 올바른 방향으로 가고 있습니까? 이것은 쉽게 (또는 전혀) 구현 될 수 있습니까?

감사합니다.

+2

매개 변수 수가 다른 함수를 미리 모르는 경우, 콜백을 호출 할 코드의 인수로 호출에 전달할 값은 어디에서 얻습니까? –

+0

lambdas가 필요합니다. 컴파일러가 아직 그들을 지원합니까? 그렇지 않다면 부스트의 피닉스 게으른 기능을보십시오. –

+0

먼저 매개 변수를 어떻게 가져 옵니까? –

답변

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; 

} 

는 동일한 유형을 리턴하는 함수가 많은 경우, 적절한 getResult를을 (구현 FuncBase의 서브 클래스)를 정의 할 수 있으며, 이러한 기능에 대한 래퍼를 하위 클래스로 할 수 있습니다. void를 반환하는 함수는 물론 래퍼 클래스에 GetResult()가 필요하지 않습니다.

+0

이것은 정확히 내가 찾고 있던 ... 감사합니다. –

1

당신이하려는 일은 거의 불가능합니다. 대신 매개 변수를 std::vector<boost::any>과 같은 것으로 포장하는 것이 좋습니다.

가변 매개 변수 목록은 실제로 원하는 것과 반대입니다. 다양한 매개 변수 목록을 사용하면 매개 변수 집합이 고유 한 여러 사이트에서 단일 함수를 호출 할 수 있습니다. 원하는 것은 단일 사이트에서 여러 개의 함수를 호출하는 것입니다. 각 함수는 고유 한 매개 변수 집합을 가지고 있으며 가변 매개 변수 목록은이를 지원하지 않습니다.

1

c/invoke은 런타임에 임의의 함수 호출을 구성 할 수있는 라이브러리이지만이 경우에는 과도하다고 생각합니다. 콜백 함수의 서명을 "표준화"하는 방법을 찾아서 동일한 인터페이스를 통해 다른 데이터를 전달할 수있는 목록, 구조체, 공용체 또는 다른 것과 같은 방식으로 매번 호출 할 수있는 것처럼 보입니다.

3

생각해 보면 boost::bind이 유용 할 것입니다. 응용 프로그램에서는 큐에 넣기 전에 (즉, _1 또는 _2 자리 표시자를 사용하지 말고) 펑터를 만들 때 모든 인수를 바인딩 할 수 있습니다. 나는 당신이 lambda expressions/abstractions처럼 복잡 할 필요는 없다고 생각하지만, 그것이 무엇인지 이해하는 것이 좋습니다.

+1 DIY 접근 방식에 대한 CEO.그것도 효과가 있지만, 모든 노력을 스스로해야합니다.

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_; 
}; 
+0

API 호출은 반환 형식과 매개 변수의 조합이 너무 많지 않기 때문에 CEO의 접근 방식을 사용하게되었습니다. 그러나, 나는 더 큰 범위를 가진 프로젝트를 위해이 방법을 사용하는 이점을 확실히 볼 수 있습니다 ... 감사합니다! –

+0

좋은 직장.나는 이것의 templatized 버전을 해결하려고 노력했지만, 그 시점까지 조금 더 늦게지고 있었다. – ceo

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); 
} 
1

@Redef, 컴파일러가 레지스터에 인수를 최적화하는 경우가 vargs이되지 아니하는 한,이 스택에 그들을 밀어 필요가 없다. 즉, 첫 번째 예에서는 콜백 함수가 INTFUNC (vargs decl과 함께)를 사용하는 호출자가 스택에 푸시하는 동안 콜백 함수가 레지스터에서 args를 기다리는 것을 의미합니다.

결과는 콜백에 args가 표시되지 않습니다.