C ++, как обобщить параметр функции класса для обработки многих типов указателей на функции? - PullRequest
2 голосов
/ 20 июня 2011

Я не знаю, выполнимо ли это, глупо или просто. Я только недавно начал изучать шаблонные функции и классы, и мне было интересно, возможен ли следующий сценарий: Класс, который содержит указатель на функцию для вызова. Указатель на функцию не может быть конкретным, но абстрактным, поэтому всякий раз, когда вызывается конструктор класса, он может принимать различные виды указателей на функции. Когда вызывается функция execute класса, он выполняет указатель функции, выделенный при построении, с аргументом (или аргументами). По сути, абстракция сохраняется на протяжении всего проекта и остается на усмотрение пользователя о том, какую функцию указатель и аргументы передать. Следующий код не был протестирован, просто чтобы продемонстрировать, что я пытаюсь сделать:

void (*foo)(double);
void (*bar)(double,double);
void (*blah)(float);

class Instruction
{
    protected:
      double var_a;
      double var_b;
      void (*ptr2func)(double);
      void (*ptr2func)(double,double);  
    public:
      template <typename F> Instruction(F arg1, F arg2, F arg3)
      {
         Instruction::ptr2func = &arg1;
         var_a = arg2;
         var_b = arg3;
      };    
      void execute()
      {
         (*ptr2func)(var_a);
      };
};

Мне не нравится тот факт, что я должен держать список внутри класса возможных перегружаемых указателей на функции. Как я мог улучшить вышеприведенное, чтобы максимально обобщить его, чтобы он мог работать с любым указателем функции, брошенным на него? Имейте в виду, я хочу сохранить контейнер этих созданных объектов и выполнить каждый указатель функции в последовательности. Спасибо ! Изменить: Может быть, класс должен быть сам шаблон, чтобы облегчить использование со многими различными указателями функций? Edit2: я нашел способ обойти мою проблему только для дальнейшего использования, не знаю, является ли она правильной, но она работает:

class Instruction
{
  protected:
    double arg1,arg2,arg3;
  public:
    virtual void execute() = 0;
};

template <class F> class MoveArm : public Instruction
{
  private:
    F (func);    
  public:
   template <typename T> 
   MoveArm(const T& f,double a, double b)
   {
     arg1 = a;
     arg2 = b;
     func = &f;
   };

   void execute()
   {
     (func)(arg1,arg2);
   };
};

Однако при импорте функций их указатели функций должны быть typedef'd:

void doIt(double a, double b)
{
   std::cout << "moving arm at: " << a << " " << b << std::endl;
};

typedef void (*ptr2func)(double,double);

int main(int argc, char **argv) {

   MoveArm<ptr2func>  arm(doIt,0.5,2.3);
   arm.execute();

   return 0;
}

Ответы [ 3 ]

5 голосов
/ 20 июня 2011

Если вы можете использовать C ++ 0x и шаблоны variadic, вы можете достичь этого, используя комбинацию std::function, std::bind и совершенную пересылку:

#include <iostream>
#include <functional>

template <typename Result = void>
class instruction
{
public:
        template <typename Func, typename... Args>
        instruction(Func func, Args&&... args)
        {
                m_func = std::bind(func, std::forward<Args>(args)...);
        }

        Result execute()
        {
                return m_func();
        }
private:
        std::function<Result ()> m_func;
};

double add(double a, double b)
{
        return (a + b);
}

int main()
{
        instruction<double> test(&add, 1.5, 2.0);
        std::cout << "result: " << test.execute() << std::endl;
}

Пример с выводом: http://ideone.com/9HYWo

В C ++ 98/03, к сожалению, вам придется перегружать конструктор для N-параметров самостоятельно, если вам нужно поддерживать переменное число аргументов. Вы также использовали бы boost::function и boost::bind вместо std:: эквивалентов. Кроме того, существует проблема проблема пересылки , поэтому для идеальной пересылки вам потребуется экспоненциальное количество перегрузок в зависимости от количества аргументов, которые необходимо поддерживать. Boost имеет библиотеку препроцессора , которую вы можете использовать для генерации требуемых перегрузок без необходимости записывать все перегрузки вручную; но это довольно сложно.

Вот пример того, как сделать это с C ++ 98/03, предполагая, что функции, которые вы передаете в instruction, не должны принимать аргументы по изменяемой ссылке, для этого вам также нужно иметь перегрузки для P1& p1 вместо просто const P1& p1.

#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>

template <typename Result = void>
class instruction
{
public:
        template <typename Func>
        instruction(Func func)
        {
                m_func = func;
        }

        template <typename Func, typename P1>
        instruction(Func func, const P1& p1)
        {
                m_func = boost::bind(func, p1);
        }

        template <typename Func, typename P1, typename P2>
        instruction(Func func, const P1& p1, const P2& p2)
        {
                m_func = boost::bind(func, p1, p2);
        }

        template <typename Func, typename P1, typename P2, typename P3>
        instruction(Func func, const P1& p1, const P2& p2, const P3& p3)
        {
                m_func = boost::bind(func, p1, p2, p3);
        }

        Result execute()
        {
                return m_func();
        }
private:
        boost::function<Result ()> m_func;
};

double add(double a, double b)
{
        return (a + b);
}

int main()
{
        instruction<double> test(&add, 1.5, 2.0);
        std::cout << "result: " << test.execute() << std::endl;
}

Пример: http://ideone.com/iyXp1

2 голосов
/ 20 июня 2011

Я также создал версию C ++ 0x с некоторыми примерами использования.Вы, вероятно, можете лучше использовать тот, который дал reko_t, но я, тем не менее, опубликовал этот.Этот использует рекурсию, чтобы распаковать кортеж со значениями, и, следовательно, кортеж для хранения аргументов для передачи в функцию.Обратите внимание, что этот не использует идеальную пересылку.Если вы используете это, вы, вероятно, хотите добавить это.

#include <iostream>
#include <string>
#include <tuple>

using namespace std;

template<unsigned N>
struct FunctionCaller
{
    template<typename ... Typenames, typename ... Args>
    static void call(void (*func)(Typenames ...), tuple<Typenames ...> tuple, Args ... args)
    {
        FunctionCaller<N-1>::call(func, tuple, get<N-1>(tuple), args ...);
    }
};

template<>
struct FunctionCaller<0u>
{
    template<typename ... Typenames, typename ... Args>
    static void call(void (*func)(Typenames ...), tuple<Typenames ...> tuple, Args ... args)
    {
        func(args ...);
    }
};

template<typename ... Typenames>
class Instruction
{
    public:
    typedef void (*FuncType)(Typenames ...);

    protected:
    std::tuple<Typenames ...> d_args;
    FuncType d_function;

    public:
    Instruction(FuncType function, Typenames ... args):
        d_args(args ...),
        d_function(function)
    {
    }

    void execute()
    {
        FunctionCaller<sizeof...(Typenames)>::call(d_function, d_args);
    }
};

void test1()
{
    cout << "Hello World" << endl;
}

void test2(int a, string b, double c)
{
    cout << a << b << c << endl;
}

int main(int argc, char** argv)
{
    Instruction<> i1(test1);
    Instruction<int, string, double> i2(test2, 5, "/2 = ", 2.5);
    i1.execute();
    i2.execute();
    return 0;
}
1 голос
/ 20 июня 2011

Ну, то, что вы делаете, правильно.Но так как все указатели имеют одинаковый размер в C ++, вы можете сохранить один указатель (типа void):

void *funcptr;

и при необходимости привести его к нужному типу:

static_cast<(*void)(double,double)>(funcptr)(var_a, var_b);

Нопожалуйста, используйте это только тогда, когда нельзя использовать лучшие методы, но я не могу сказать, если вы не сообщите нам более широкую картину.


Возможно, вы захотите взглянуть на boost::function.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...