Pergunta

Eu estou trabalhando na implementação de uma função que execute uma outra função de alguns segundos, no futuro, dependendo de entrada do usuário.Eu tenho uma fila de prioridade de uma classe (o que eu estou chamando TimedEvent) que contém um ponteiro de função para a ação que deseja executar no final do intervalo.Dizer, por exemplo, que o usuário quer que o programa para chamar uma função "xyz" depois de 3 segundos, eles iriam criar um novo TimedEvent com o tempo e o ponteiro de função para xyz e adicioná-lo à fila de prioridade (o que é ordenado pelo tempo, com o mais logo eventos a acontecer primeiro).

Eu tenho sido capaz de obter a fila de prioridade para retirar o elemento do topo após o tempo especificado, mas estou correndo em uma parede aqui.As funções que eu quero chamar poderia levar a uma variedade de diferentes parâmetros, desde que levam apenas um único inteiro que levam 3 números inteiros, uma seqüência de caracteres, etc.e também retornam valores diferentes (algumas interrupções, algumas cadeias de caracteres, etc.).Eu olhei em va_lists (que eu não tenho nenhuma experiência com), mas este não parece ser a resposta, a menos que eu estou faltando alguma coisa.

Em resumo (o TL;DR a versão):
Eu gostaria de ser capaz de chamar essas funções, como "diversas", como esses com o mesmo ponteiro de função:

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

Estou no caminho certo, com um va_list e uma função de retorno de chamada?Isso pode ser implementado facilmente (ou em todos)?

Obrigado!

Foi útil?

Solução

Estou deduzindo aqui que essas funções são chamadas de API pelas quais você não tem controle. Eu cortei algo que acho que faz mais ou menos o que você está procurando; É uma espécie de padrão de comando aproximado.

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

}

Se você tiver muitas funções retornando ao mesmo tipo, poderá definir uma subclasse de funcase que implementa o GetResult () e os invólucros apropriados para essas funções, podem subcláticas. As funções que retornam o vazio não exigiriam um getResult () em sua classe de invólucro, é claro.

Outras dicas

Eu penso Boost :: bind será útil para você. Para o seu aplicativo, você provavelmente desejará vincular todos os argumentos quando criar o functor, antes de colocá -lo na fila (ou seja, não use nenhum espaço reservado _1 ou _2). Eu não acho que você precisa de nada tão complicado quanto Expressões/abstrações lambda, mas é bom entender o que eles são.

+1 CEO para a abordagem de bricolage. Isso também funcionará, mas você precisa fazer todo o trabalho duro sozinho.

Se você quiser DIY, sugiro o uso de modelos em vez de definir um XFUNC e XFUNCRAPRER para cada combinação de tipos (consulte o código abaixo).

Além disso, acho que permitir que diferentes tipos de retorno serão inúteis-qualquer código que esteja despercebendo e chamar as funções será genérico. Ou espera o mesmo tipo de retorno de cada função ou espera que sejam procedimentos (retorno vazio).

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

O que você está tentando fazer é quase impossível chegar ao trabalho.Você pode querer considerar a embalagem de seus parâmetros em algo como um std::vector<boost::any> em vez disso.

Parâmetro variável de listas é realmente o oposto do que você quer.Uma variável de lista de parâmetro permite que uma única função para ser chamado a partir de vários sites, cada um com um conjunto único de parâmetros.O que você quer é chamar várias funções a partir de um único site, cada um com um conjunto único de parâmetros e um parâmetro de variável de lista, só não apoio isso.

c/Invoque é uma biblioteca que permite construir chamadas de função arbitrária em tempo de execução, mas acho que isso é um exagero neste caso. Parece que você deve encontrar uma maneira de "normalizar" a assinatura da função de retorno de chamada, para que você possa chamá -lo da mesma maneira sempre com uma lista, estrutura, união ou algo que permite passar dados diferentes pela mesma interface.

Bem, há um verdadeiro truque hardcore que explora o fato de que, em C, todas as funções são um ponteiro e você pode lançar um ponteiro para qualquer outro ponteiro. O código original, de onde eu recebi isso, foi escrito, quando os compiladores não deram erros em elencos implícitos, então demorei um pouco para descobrir que eu tinha que lançar as funções. O que faz é que ele lança a função de retorno de chamada para uma função com um número variável de argumentos. Mas, ao mesmo tempo, a função de invocação é fundida para uma função com 10 argumentos, dos quais nem todos serão fornecidos. Especialmente este último passo parece complicado, mas você já viu antes, onde você dá o número errado de argumentos para printf e ele apenas compila. Pode até ser isso que é isso que o va_start/va_end faz sob o capô. O código é realmente para fazer uma operação personalizada em qualquer elemento no banco de dados, mas também pode ser usado para sua situação:

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

}

A mesma funcionalidade pode ser perfeitamente gravada usando VA_START/VA_END. Pode ser a maneira mais oficial de fazer as coisas, mas acho isso menos amigável. A função de chamada de chamada precisa decodificar seus argumentos ou você precisa escrever um bloco de comutador/caixa dentro da função de invocação para cada combinação de argumentos que a função de retorno de chamada pode ter. Isso significa que você deve fornecer o formato dos argumentos (assim como o printf) ou precisar que todos os argumentos sejam iguais e você apenas forneça o número de argumentos, mas ainda precisa escrever um caso para cada quantidade de argumentos. Aqui está um exemplo em que a função de retorno de chamada decodifica os argumentos:

#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, se o seu compilador otimizar o ARGS nos registros, ele não precisará empurrá -los na pilha, a menos que sejam vargs. Isso significa que, em seu primeiro exemplo, que a Function de Retorno espera args nos registros enquanto o chamador usando o Intfunc (com um Vargs Decl) os empurra na pilha.

O resultado será que o retorno de chamada não vê os args.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top