2

ユーザーの入力に応じて、数秒後に別の関数を実行する関数の実装に取り​​組んでいます。間隔の最後に実行するアクションへの関数ポインターを含むクラス (TimedEvent を呼び出す) の優先度キューがあります。たとえば、ユーザーがプログラムに関数「xyz」を 3 秒後に呼び出すようにさせたい場合、時間と xyz への関数ポインターを使用して新しい TimedEvent を作成し、それを優先キュー (時間でソートされ、最初に発生する最も早いイベント)。

指定された時間の経過後、優先キューが一番上の要素から飛び出すことに成功しましたが、ここで壁にぶつかっています。呼び出したい関数は、単一の整数のみを受け取るものから、3 つの整数、文字列などを受け取るものまで、さまざまな異なるパラメーターを取り、異なる値 (いくつかの int、いくつかの文字列など) を返すこともできます。私は 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

boost::bindが役立つと思います。アプリケーションでは、ファンクターを作成するときに、キューに入れる前にすべての引数をバインドする必要があるでしょう (つまり、_1 または _2 プレースホルダーを使用しないでください)。ラムダ式/抽象化ほど複雑なものは必要ないと思いますが、それらが何であるかを理解することは良いことです。

DIY アプローチのための +1 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_;
};
于 2009-12-04T08:02:41.397 に答える
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

あなたがやろうとしていることは、仕事に就くことはほとんど不可能です。パラメーターを代わりに のようなものにパックすることを検討することをお勧めしますstd::vector<boost::any>

可変パラメーター リストは、実際には必要なものとは逆です。変数パラメーター リストを使用すると、それぞれが一意のパラメーター セットを使用して、複数のサイトから 1 つの関数を呼び出すことができます。あなたが望むのは、単一のサイトから複数の関数を呼び出し、それぞれに固有のパラメーター セットを使用することです。可変パラメーター リストはそれをサポートしていません。

于 2009-12-03T23:57:07.587 に答える
1

@Redef、コンパイラが引数をレジスタに最適化する場合、vargs でない限りスタックにプッシュする必要はありません。これは、最初の例では、INTFUNC (vargs decl を使用) を使用している呼び出し元がそれらをスタックにプッシュしている間、コールバック関数がレジスタ内の引数を期待することを意味します。

その結果、コールバックは引数を認識しません。

于 2012-06-14T06:27:29.093 に答える
1

c/invokeは、実行時に任意の関数呼び出しを作成できるライブラリですが、この場合はやり過ぎだと思います。コールバック関数のシグネチャを「正規化」する方法を見つけて、リスト、構造体、ユニオン、または同じインターフェイスを介して異なるデータを渡すことができるものを使用して毎回同じ方法で呼び出すことができるようにする必要があるようです。

于 2009-12-04T00:12:02.797 に答える