【问题标题】:C++ how to generalize class function parameter to handle many types of function pointers?C++如何泛化类函数参数来处理多种类型的函数指针?
【发布时间】:2011-06-20 14:12:56
【问题描述】:

我不知道我问的是可行的、愚蠢的还是简单的。 我最近才开始研究模板函数和类,我想知道以下情况是否可能: 包含要调用的函数指针的类。函数指针不能是具体的,而是抽象的,这样每当类的构造函数被调用时,它就可以接受不同种类的函数指针。当调用类的执行函数时,它会执行在构造时分配的函数指针,并带有一个(或多个)参数。 基本上,在整个设计中都保留了抽象,并将传递什么函数指针和参数留给用户。以下代码未经测试,只是为了演示我正在尝试做的事情:

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:

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

【问题讨论】:

  • boost::functionboost::bind 如果你想要模板函子?
  • 好像你在重新发明boost::function: boost.org/doc/libs/1_46_1/doc/html/function/…
  • 对我来说,听起来他想保存具有任意签名的函数指针/对象;这不是boost::function 所做的事情。您应该考虑使用 std::bind / boost::bind 创建一个包装参数的函子。
  • 我不知道boost已经有这个功能,但即使有,我还是想了解这是怎么做到的。将研究 boost 以了解它是如何工作的。谢谢。

标签: c++ templates function pointers abstraction


【解决方案1】:

如果你可以使用C++0x和可变参数模板,你可以结合std::functionstd::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::functionboost::bind 而不是 std:: 等价物。然后还有forwarding problem 的问题,因此要进行完美转发,您需要根据需要支持的参数数量进行指数级的重载。 Boost 有一个preprocessor library,您可以使用它来生成所需的重载,而无需手动编写所有重载;但这很复杂。

这是一个如何使用 C++98/03 执行此操作的示例,假设您传递给 instruction 的函数不需要通过可修改引用获取参数,为此,您还需要P1&amp; p1 的重载,而不仅仅是 const P1&amp; 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

【讨论】:

  • 我正在查看最多三个参数,通常是浮点数或双精度数,所以我不太关心参数的数量。我关心函数签名和返回类型。
  • 您是否需要通过非常量引用获取参数,或者它们总是通过值或常量引用传递?如果您不需要支持非常量引用,则只需要与需要支持的参数数量一样多的重载(在您的情况下为 3)。
  • 通常按值、指针或常量引用。在任何情况下,我都可以为每种情况使用三个(或四个)重载的构造函数。我的问题是:当函数指针返回一些东西(布尔值或双精度值)时,我怎么知道呢?有些函数是无效的,有些函数确实返回了一些东西。在这种情况下我该怎么办?
  • 我修改了 C++0x 示例以显示如何处理返回值,并为 C++98/03 添加了一个示例以显示最多 3 个参数。
  • 非常感谢!我可能最终会使用它,但出于好奇,我不能制作一个模板抽象基类来分配函数指针,并从那里使用它来重载带有参数的继承类构造函数吗?
【解决方案2】:

我还创建了一个带有一些示例用法的 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;
}

【讨论】:

  • 这是一个很好的例子,展示了如何传递和存储任意数量的参数并在以后解压它们。但是是否有任何理由不通过右值引用传递参数包以避免不必要的复制?或者你是在假设它只与floatdouble这样的类型一起使用的情况下写的,就像提到的OP一样。
【解决方案3】:

嗯,你所做的是正确的。但由于 C++ 中所有指针的大小相同,因此您可以存储一个指针(void 类型):

void *funcptr;

并在需要时将其转换为必要的类型:

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

但是请,只有在无法使用更好的技术时才使用它,但如果你不告诉我们更大的图景,我无法判断。


你可能想看看boost::function

【讨论】:

  • 这难道不是因为我需要知道根据使用的函数指针要转换的内容吗?我宁愿避免使用 void 指针。
  • 更大的图景是为机器人中的强化学习创建一个框架,但为了使其尽可能简单和可重用,我必须保持其通用性。函数指针通常是指向 API 调用的指针,并且由于控制器函数存在巨大差异,这会产生问题。理想情况下,该指令能够将任何指向 api 函数的函数指针作为参数,该函数通常具有从零到三个参数。然后另一个类 Action 将包含与状态相关的指令,等等......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多