【问题标题】:How to create a container that holds different types of function pointers in C++?如何在 C++ 中创建一个包含不同类型函数指针的容器?
【发布时间】:2011-06-09 14:23:20
【问题描述】:

我正在做一个线性遗传编程项目,其中程序是通过自然进化机制培育和进化的。他们的“DNA”基本上是一个容器(我已经成功地使用了数组和向量),其中包含指向一组可用函数的函数指针。 现在,对于简单的问题,例如数学问题,我可以使用一个类型定义的函数指针,它可以指向所有返回一个双精度并且都将两个双精度作为参数的函数。

不幸的是,这不是很实用。我需要能够有一个容器,它可以有不同类型的函数指针,比如一个指向不带参数的函数的函数指针,或者一个带一个参数的函数,或者一个返回某些东西的函数等(你得到想法)...

有没有办法使用任何类型的容器来做到这一点? 我可以使用包含多态类的容器来做到这一点,而多态类又具有各种函数指针? 我希望有人可以指导我找到解决方案,因为重新设计我迄今为止所做的一切都会很痛苦。

【问题讨论】:

  • 假设您有一个容器c,其中包含许多不同类型的功能。你打算怎么称呼他们?你将如何处理非void 函数的函数结果?
  • 我会通过遍历容器来调用它们,并依次执行函数指针指向的函数。我不太确定我想做的事情是否可行。对于非 void 函数,我只需将返回值分配给 void 指针并尝试处理它(这甚至可能吗?)。
  • 为什么要存储函数指针?为什么不只存储一个标识符,您可以使用它来选择要执行的函数? (例如, switch(FuncId){ case MULT: ... break; } )无论哪种方式,您都需要一些有意义地应用所选函数的方法(例如,函数的返回值在哪里?)

标签: c++ containers function-pointers virtual-machine genetic-programming


【解决方案1】:

虚拟机的一个典型想法是有一个单独的堆栈用于参数和返回值传递。

您的函数仍然可以都是 void fn(void) 类型,但您需要手动传递和返回参数。

你可以这样做:

class ArgumentStack {
    public:
        void push(double ret_val) { m_stack.push_back(ret_val); }

        double pop() {
             double arg = m_stack.back();
             m_stack.pop_back();
             return arg;
        }

    private:
        std::vector<double> m_stack;
};
ArgumentStack stack;

...所以函数可能如下所示:

// Multiplies two doubles on top of the stack.
void multiply() {
    // Read arguments.
    double a1 = stack.pop();
    double a2 = stack.pop();

    // Multiply!
    double result = a1 * a2;

    // Return the result by putting it on the stack.
    stack.push(result);
}

可以这样使用:

// Calculate 4 * 2.
stack.push(4);
stack.push(2);
multiply();
printf("2 * 4 = %f\n", stack.pop());

你关注吗?

【讨论】:

  • 不,不是真的,很遗憾,因为您似乎在这里有一些东西...您有链接吗?我可以找到有关自定义堆栈的更多信息?
  • 也许你可以尝试学习一点 Forth。 en.wikipedia.org/wiki/Forth_%28programming_language%29 它使用相同的方法,这也启发了 Java VM(如果我是对的)如果您需要更多帮助,请再次询问。
  • 这似乎可以通过一些调整来工作,但这只会改变传递给函数指针的参数。这是否也必须对函数指针指向的函数进行?关于 FORTH,这真的很有趣,但遗憾的是我现在没有时间将所有这些内容改编成另一种语言或学习它,但是谢谢你让我知道!
  • @Alex:是的,函数需要重写,但是您只需要更改与参数传递和返回值相关的部分 - 相当琐碎。
【解决方案2】:

你不能将多态函数放在一个类中,因为接受(或返回)不同事物的函数不能以相同的方式使用(具有相同的接口),这是多态性所要求的.

让一个类为你需要的任何可能的函数类型提供一个虚函数的想法是可行的,但是(对你的问题一无所知!)它的用法让我感觉奇怪:什么函数会派生类覆盖?你的函数不是不相关的吗?

如果你的函数是不相关的(如果你没有理由将它们分组为同一个类的成员,或者它们是静态函数,因为它们不需要成员变量)你应该选择别的东西......如果你随机选择你的函数,你可以有几个不同的容器,一个用于函数类型,然后随机选择一个容器,然后在其中选择一个函数。

你能举一些例子来说明你的函数是做什么的吗?

【讨论】:

  • 是的,有些函数是不相关的,但不是全部,例如用于求解一次多项式的函数集基本上是:加、减、除、乘、根、平方和交换(两个变量)和反转(从正数、负数或反之亦然)。然而,在更复杂的问题中,例如二次多项式或微分,我需要使用比较值的函数、具有“逻辑”或简单算法的函数等......这些函数可能需要参考或返回值,因此与第一组完全不同。
【解决方案3】:

您提到的本身可能可以通过一个容器来实现 std::function 或像 Boost::variant 这样的受歧视联合。
例如:

#include <functional>
#include <cstdio>
#include <iostream>

struct F {
  virtual ~F() {}
};

template< class Return, class Param = void >
struct Func : F {
  std::function< Return( Param ) >  f;
  Func( std::function< Return( Param ) > const& f ) : f( f ) {}
  Return operator()( Param const& x ) const { return f( x ); }
};

template< class Return >
struct Func< Return, void > : F {
  std::function< Return() >  f;
  Func( std::function< Return() > const& f ) : f( f ) {}
  Return operator()() const { return f(); }
};

static void f_void_void( void ) { puts("void"); }
static int f_int_int( int x ) { return x; }

int main()
{
  F  *f[] = {
    new Func< void >( f_void_void ),
    new Func< int, int >( f_int_int ),
  };

  for ( F **a = f, **e = f + 2;  a != e;  ++ a ) {
    if      ( auto p = dynamic_cast< Func< void >*     >( *a ) ) {
      (*p)();
    }
    else if ( auto p = dynamic_cast< Func< int, int >* >( *a ) ) {
      std::cout<< (*p)( 1 ) <<'\n';
    }
  }
}

但我不确定这是否真的是你想要的......
您如何看待 Alf P. Steinbach 的评论?

【讨论】:

  • 我不太确定我是否理解您的代码,但您编写的代码似乎可以工作。我没有过多考虑 Steinbach 的评论,但仍然不确定。
【解决方案4】:

这种事情可以通过一些工作来实现。首先,重要的是要理解为什么更简单的事情是不可能的:在 C/C++ 中,将参数传递给函数的确切机制以及如何从函数获取返回值取决于参数的类型(和大小)。这是在应用程序二进制接口 (ABI) 中定义的,它是一组允许由不同编译器编译的 C++ 代码进行互操作的约定。该语言还指定了在调用站点发生的一系列隐式类型转换。所以简短的回答是,在 C/C++ 中,编译器不能为调用在编译时签名未知的函数发出机器代码。

现在,您当然可以在 C++ 中实现 Javascript 或 Python 之类的东西,其中所有值(与这些函数相关)都是动态类型的。你可以有一个基本的“值”类,它可以是整数、浮点数、字符串、元组、列表、映射等。你可以使用std::variant,但我认为这实际上在语法上很麻烦,你最好这样做自己动手:

enum class Type {integer, real, str, tuple, map};

struct Value
{
  // Returns the type of this value.
  virtual Type type() const = 0;
  
  // Put any generic interfaces you want to have across all Value types here.
};

struct Integer: Value
{
  int value;

  Type type() const override { return Type::integer; }
};

struct String: Value
{
  std::string value;

  Type type() const override { return Type::str; }  
};

struct Tuple: Value
{
  std::vector<Value*> value;

  Type type() const override { return Type::tuple; };
}

// etc. for whatever types are interesting to you.

现在您可以将函数定义为接受单个 Value* 并返回单个 Value* 的任何内容。多个输入或输出参数可以作为元组或映射传入:

using Function = Value* (*)(Value*);

您的所有函数实现都需要获取类型并使用参数做一些适当的事情:

Value* increment(Value* x)
{
  switch (x->type())
  {
    Type::integer:
      return new Integer(((Integer*) x)->value + 1);
    Type::real:
      return new Real(((Real*) x)->value + 1.0);
    default:
      throw TypeError("expected an integer or real argument.")
  }
}

increment 现在与Function 类型兼容,可以存储在mFuncs 中。您现在可以在未知类型的参数上调用未知类型的函数,如果参数不匹配,您将得到一个异常,或者如果参数兼容,您将得到一个未知类型的结果。

您很可能希望将函数签名存储为您可以自省的东西,即动态计算Function 采用的参数的数量和类型。在这种情况下,您可以使用必要的自省函数创建一个基类 Function 并为其提供一个 operator () 以使其看起来像调用常规函数。然后根据需要派生和实现Function

这是一个草图,但希望包含足够的指针来指明方向。还有更多类型安全的方法来编写此代码(我喜欢 C 风格的强制转换,因为我已经检查了类型,但有些人可能坚持你应该改用 dynamic_cast),但我认为这不是重点这个问题的。您还必须弄清楚如何管理 Value* 对象的生命周期,这是一个完全不同的讨论。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-06
    • 2020-09-10
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    • 2019-05-06
    • 2020-05-28
    • 2021-11-04
    相关资源
    最近更新 更多