1

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

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

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

4

4 回答 4

2

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

您的函数仍然可以都是 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());

你跟吗?

于 2011-01-15T10:30:27.903 回答
1

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

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

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

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

于 2011-01-15T10:14:42.350 回答
1

您提到的内容可能可以通过容器 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 的评论?

于 2011-01-15T12:49:41.810 回答
0

这种事情可以通过一些工作来实现。首先,重要的是要理解为什么更简单的事情是不可能的:在 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.

现在,您可以将函数定义为任何接受 singleValue*并返回 single的东西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. 您现在可以在未知类型的参数上调用未知类型的函数,如果参数不匹配,您将得到异常,或者如果参数兼容,则会得到某种未知类型的结果。

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

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

于 2021-12-08T20:01:29.473 回答