如何在C++中创建一个保存不同类型函数指针的容器?

发布于 2024-10-12 07:14:48 字数 376 浏览 9 评论 0原文

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

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

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

I'm doing a linear genetic programming project, where programs are bred and evolved by means of natural evolution mechanisms. Their "DNA" is basically a container (I've used arrays and vectors successfully) which contain function pointers to a set of functions available.
Now, for simple problems, such as mathematical problems, I could use one type-defined function pointer which could point to functions that all return a double and all take as parameters two doubles.

Unfortunately this is not very practical. I need to be able to have a container which can have different sorts of function pointers, say a function pointer to a function which takes no arguments, or a function which takes one argument, or a function which returns something, etc (you get the idea)...

Is there any way to do this using any kind of container ?
Could I do that using a container which contains polymorphic classes, which in their turn have various kinds of function pointers?
I hope someone can direct me towards a solution because redesigning everything I've done so far is going to be painful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

記憶穿過時間隧道 2024-10-19 07:14:48

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

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

你遵循吗?

A typical idea for virtual machines is to have a separate stack that is used for argument and return value passing.

Your functions can still all be of type void fn(void), but you do argument passing and returning manually.

You can do something like this:

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;

...so a function could look like this:

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

This can be used in this way:

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

Do you follow?

烟凡古楼 2024-10-19 07:14:48

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

让一个类为您需要的任何可能的函数类型提供虚拟函数的想法是可行的,但是(在不了解您的问题的情况下!)它的用法对我来说感觉很奇怪:派生类将具有哪些函数覆盖?你们的功能不是不相关的吗?

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

你能举一些例子来说明你的函数的作用吗?

You cannot put a polymorphic function in a class, since functions that take (or return) different things cannot be used in the same way (with the same interface), which is something required by polymorphism.

The idea of having a class providing a virtual function for any possible function type you need would work, but (without knowing anything about your problem!) its usage feels weird to me: what functions would a derived class override? Aren't your functions uncorrelated?

If your functions are uncorrelated (if there's no reason why you should group them as members of the same class, or if they would be static function since they don't need member variables) you should opt for something else... If you pick your functions at random you could just have several different containers, one for function type, and just pick a container at random, and then a function within it.

Could you make some examples of what your functions do?

黑白记忆 2024-10-19 07:14:48

你提到的本身可以通过一个容器来实现
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';
    }
  }
}

但我不确定这是否真的是您想要的...
您如何看待阿尔夫·P·斯坦巴赫的评论?

What you mentioned itself can be implemented probably by a container of
std::function or discriminated union like Boost::variant.
For example:

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

But I'm not sure this is really what you want...
What do you think about Alf P. Steinbach's comment?

小糖芽 2024-10-19 07:14:48

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

现在,您当然可以用 C++ 实现 Javascript 或 Python 之类的东西,其中所有值(与这些函数相关)都是动态输入的。您可以有一个基本的“Value”类,它可以是整数、浮点数、字符串、元组、列表、映射等。您可以使用 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类,并为其提供一个operator ()以使其看起来像调用常规函数。然后,您可以根据需要派生并实现Function

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

This sort of thing is possible with a bit of work. First it's important to understand why something simpler is not possible: in C/C++, the exact mechanism by which arguments are passed to functions and how return values are obtained from the function depends on the types (and sizes) of the arguments. This is defined in the application binary interface (ABI) which is a set of conventions that allow C++ code compiled by different compilers to interoperate. The language also specifies a bunch of implicit type conversions that occur at the call site. So the short and simple answer is that in C/C++ the compiler cannot emit machine code for a call to a function whose signature is not known at compile time.

Now, you can of course implement something like Javascript or Python in C++, where all values (relevant to these functions) are typed dynamically. You can have a base "Value" class that can be an integer, float, string, tuples, lists, maps, etc. You could use std::variant, but in my opinion this is actually syntactically cumbersome and you're better of doing it yourself:

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.

Now you can define a function as anything that takes a single Value* and returns a single Value*. Multiple input or output arguments can be passed in as a Tuple, or a Map:

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

All your function implementations will need to get the type and do something appropriate with the argument:

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 is now compatible with the Function type and can be stored in mFuncs. You can now call a function of unknown type on arguments of unknown type and you will get an exception if the arguments don't match, or a result of some unknown type if the arguments are compatible.

Most probably you will want to store the function signature as something you can introspect, i.e. dynamically figure out the number and type of arguments that a Function takes. In this case you can make a base Function class with the necessary introspection functions and provide it an operator () to make it look something like calling a regular function. Then you would derive and implement Function as needed.

This is a sketch, but hopefully contains enough pointers to show the way. There are also more type-safe ways to write this code (I like C-style casts when I've already checked the type, but some people might insist you should use dynamic_cast instead), but I figured that is not the point of this question. You will also have to figure out how Value* objects lifetime is managed and that is an entirely different discussion.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文