English 中文(简体)
Variable number of arguments (va_list) with a function callback?
原标题:

I am working on implementing a function that would execute another function a few seconds in the future, depending upon the user s input. I have a priority queue of a class (which I am calling TimedEvent) that contains a function pointer to the action I want it to execute at the end of the interval. Say for instance that the user wants the program to call a function "xyz" after 3 seconds, they would create a new TimedEvent with the time and the function pointer to xyz and add it to the priority queue (which is sorted by time, with the soonest events happening first).

I have been able to successfully get the priority queue to pop off the top element after the specified time, but am running into a wall here. The functions I want to call could take a variety of different parameters, from ones that take only a single integer to ones that take 3 integers, a string, etc. and also return different values (some ints, some strings, etc.). I have looked into va_lists (which I have no experience with), but this doesn t seem to be the answer, unless I m missing something.

In summary (the TL;DR version):
I would like to be able to call these functions as "diverse" as these with the same function pointer:

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

Am I on the right track with a va_list and a function callback? Can this be implemented easily (or at all)?

Thanks!

最佳回答

I m inferring here that these functions are API calls that you have no control over. I hacked up something that I think does more or less what you re looking for; it s kind of a rough Command pattern.

#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;

}

If you have a lot of functions returning the same type, you can define a subclass of FuncBase that implements the appropriate GetResult(), and wrappers for those functions can subclass it. Functions returning void would not require a GetResult() in their wrapper class, of course.

问题回答

I think boost::bind will be useful to you. For your application, you will probably want to bind all arguments when you create the functor, before putting it on the queue (that is, not use any _1 or _2 placeholders). I don t think you need anything as complicated as lambda expressions/abstractions, but it s good to understand what they are.

+1 ceo for the DIY approach. That will work too, but you have to do all the hard work yourself.

If you want to DIY though, I would suggest using templates instead of defining an xfunc and XFuncWrapper for each combination of types (see code below).

Also, I think allowing different return types is going to be pointless -- whatever code is de-queuing and calling the functions is going to be generic. Either it expects the same type of return from each function, or it expects them to be procedures (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_;
};

What you re trying to do is almost impossible to get to work. You might want to consider packing your parameters into something like an std::vector<boost::any> instead.

Variable parameter lists are really the opposite of what you want. A variable parameter list allows a single function to be called from multiple sites, each with a unique set of parameters. What you want is to call multiple functions from a single site, each with a unique set of parameters -- and a variable parameter list just doesn t support that.

c/invoke is a library that lets you construct arbitrary function calls at runtime, but I think that s overkill in this case. It sounds like you should find a way to "normalize" the callback function s signature so that you can call it the same way every time with a list, structure, union or something that allows you to pass different data through the same interface.

Well, there is a real hardcore trick that exploits the fact that in C every function is a pointer and you can cast a pointer to any other pointer. The original code, where I got this from, was written, when compilers didn t gave errors on implicit casts, so it took me a while to figure out that I had to cast the functions. What it does is that it casts the callback function to a function with a variable number of arguments. But at the same time, the invocation function is cast to a function with 10 arguments, of which not all will be supplied. Especially this last step seems tricky, but you ve seen it before, where you give the wrong number of arguments to printf and it just compiles. It might even be that this is what va_start/va_end does under the hood. The code is actually for doing a custom operation on any element in the database, but it could be used for your situation as well:

#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
",DatabaseRecord,myArgument,*MyResult,DatabaseRecord,*MyResult+DatabaseRecord);
        *MyResult+=DatabaseRecord;}
    else{
        printf("mapfunction record:%d<%d not true
",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
",Result);

}

The same functionality can perfectly be written by using va_start/va_end. It might be the more official way of doing things, but I find it less user friendly. Either the callbackfunction needs to decode its arguments or you need to write a switch/case block inside the invocation function for every combination of arguments that the callback function can have. This means that you have to supply the format of the arguments (just like printf does) or you have to require that all arguments are the same and you just supply the number of arguments, but then you still have to write a case for each amount of arguments. Here is an example where the callback function decodes the arguments:

#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
",DatabaseRecord,myArgument,*MyResult,DatabaseRecord,*MyResult+DatabaseRecord);
        *MyResult+=DatabaseRecord;}
    else{
        printf("mapfunction record:%d<%d not true
",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
",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
",Result);
}

@Redef, if your compiler optimizes args into registers, it need not push them on the stack unless they are vargs. This means, in your first example, that callbackfunction will be expecting args in registers whilst the caller using INTFUNC (with a vargs decl) pushes them on the stack.

The result will be that the callback doesn t see the args.





相关问题
Undefined reference

I m getting this linker error. I know a way around it, but it s bugging me because another part of the project s linking fine and it s designed almost identically. First, I have namespace LCD. Then I ...

C++ Equivalent of Tidy

Is there an equivalent to tidy for HTML code for C++? I have searched on the internet, but I find nothing but C++ wrappers for tidy, etc... I think the keyword tidy is what has me hung up. I am ...

Template Classes in C++ ... a required skill set?

I m new to C++ and am wondering how much time I should invest in learning how to implement template classes. Are they widely used in industry, or is this something I should move through quickly?

Print possible strings created from a Number

Given a 10 digit Telephone Number, we have to print all possible strings created from that. The mapping of the numbers is the one as exactly on a phone s keypad. i.e. for 1,0-> No Letter for 2->...

typedef ing STL wstring

Why is it when i do the following i get errors when relating to with wchar_t? namespace Foo { typedef std::wstring String; } Now i declare all my strings as Foo::String through out the program, ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

Window iconification status via Xlib

Is it possible to check with the means of pure X11/Xlib only whether the given window is iconified/minimized, and, if it is, how?

热门标签