如何创建一个可以接受不同参数的函数指针向量

How do you create a vector of function pointers that can take different arguments?

本文关键字:参数 指针 函数 向量 创建 何创建 一个      更新时间:2023-10-16

我正在学习如何在std::vector中存储函数(或者更确切地说是函数指针)。我有这个代码:

#include <iostream>
#include <vector>
void x(int i)
{
std::cout << "x is " << i << std::endl;
}
void y(int i, int j)
{
std::cout << "y is " << i << " and " << "j" << std::endl;
}
int main()
{
std::vector<void(*)(int)> V;
V.push_back(x);
V[0](1);
return 0;
}

这很好,但问题是我不能把函数y推到同一个向量中,因为它需要2个整数而不是1。

如何将两个函数存储在同一个向量中?

没有什么好方法可以做你想做的事,但你可以做到。

编写一个增强的变体(std或boost或手动标记的typesafe-union),如果您的类型错误,则支持从的隐式强制转换(如果需要,请随时支持类型之间的转换)。称此poly_arg<Ts...>

编写一个以arg类型作为模板参数的类型擦除器。然后,它在构造时获取一个函数指针,并用一个长度刚好合适的参数向量擦除对它的调用。(或者一个函数对象和一个arg计数范围)。然后,它有一个varargoperator(),它将参数转发到其arg类型的向量中,然后尝试使用上述类型的擦除进行调用。如果传递了错误数量的参数,则会引发异常。称之为vararg_func<T>

存储vararg_func<poly_arg<int, double, std::string>>的矢量(3种类型的列表只是一个示例)。它可以存储void(*)(int)void(*)(int,int)void(*)(std::string, double, int)void(*)()等,您可以调用它。如果您的参数计数错误,则会得到一个异常(来自vararg func)。如果您得到一个错误的参数类型,异常(来自poly-arg)。如果您传递了一个不兼容的函数指针,那么在push_back处编译错误(这太棒了!)

如果您只需要支持int参数,则可以跳过poly_arg并存储vararg_func<int>

我认为这是个糟糕的计划。

您很少希望统一处理具有不同数量和类型参数的函数。少数合法的情况最好用两个耦合的类型擦除系统来处理(比如具有非均匀签名的高效大规模定制点表),它们在内部隐藏不安全的类型。

相反,这个计划符合您的需求,这会迫使接口中的类型不安全,并用"dunno,也许它会工作"调用污染您的代码。

如果你想帮助实现这些类型擦除器,请意识到我都知道如何编写它们,也知道它们是如何解决你的问题的,在我看来,它们真的是个坏主意。如果这不能阻止你,那就去学习C++中的类型擦除、值类型多态性以及std::function是如何工作的。试着写一个玩具std::function。播放"仅查看"answers"仅移动"版本。尝试使用有界函数对象大小的零分配。这应该需要几周或几年的时间。

现在写一些更简单的案例,比如打印到ostream。做得足够好。在这一点上,vararg_func应该是具有挑战性但可行的;尝试一下。如果失败了,请SO帮忙,包括你的尝试。

CCD_ 15应该比较容易。

您想要的既不可能也不合理。这是不可能的,因为函数指针是类型化的,并且指向void(int, int)的指针与指向void(int)的指针的类型不同。vector是一个均质容器;它的所有元素都必须是相同的类型。这两种类型是不相关的;不能将指向一种类型的指针强制转换为指向另一种类型并期望调用它。

您能做的最好的事情就是使用指向不同函数类型的指针的variant。现在,我不知道将如何调用这些函数,因为不同的类型采用不同的参数列表。你怎么能通过visit或函子来调用它呢?您是否有足够的参数转发到有问题的函数?如果没有,那又有什么意义呢?

除非你事先知道列表中的索引X有一个特定的参数列表,并且你有这些参数要传递给它,否则就没有有效的方法来调用它。如果你知道这一点,那么你可能想要的是函数指针的tuplestruct,而不是它们的运行时容器。

如果您可以访问C++17:,则可以使用std::variant

#include <variant>
std::vector<std::variant<void(*)(int), void(*)(int, int)>> V;
V.push_back(x);
V.push_back(y);

但这很快就会变得一团糟(如果你想添加更多的函数类型等),而且由于有不同的参数类型和数量,除非你还存储了它们的类型信息和正确的变体std::get,否则没有确定的方法从向量外统一调用它们。

首先,我建议在函数指针上使用std::function。它们更通用,可以用函数指针、函数对象或lambda表达式填充。典型的用途如下:

#include <iostream>
#include <functional>

struct Funktor { // This is a callable class/object
void operator()() {
std::cout << "Funktor called." << std::endl;   
}
};
void function() { // Normal function
std::cout << "Function called." << std::endl;
};
int main()
{
std::function<void()> lambdaFunction = [](){ std::cout << "lambda function executed." << std::endl;}; // And a lambda expression (fancy way to write a function where you need it)
std::function<void()> functionPointer = &function;
std::function<void()> callableObject = Funktor();
//This is the way you call functions with a std::function object, just like with a normal function
lambdaFunction(); 
functionPointer();
callableObject();
return 0;
}

但这并不能解决在std::vector中存储具有不同参数的函数的问题。既然它们有不同的签名,你就必须把它们当作不同的类型来对待。如CCD_ 27和CCD_。

为了存储不同类型的元素,STL提供std::tuple。你可以用这个来实现你的目标。

#include <iostream>
#include <functional>
#include <tuple>

int main()
{
// std::tuple takes multiple template arguments. Each corresponds to one element in the tuple
std::tuple<
std::function<void()>,
std::function<void(int)>
> functionTuple;
// To access a element of the tuple we call std::get<i> on the tuple
// This will return a reference to the element in the tuple and we
// can overwrite it with whatever we want
std::get<0>(functionTuple) = [](){
std::cout << "Function without arguments." << std::endl;
};
std::get<1>(functionTuple) = [](int arg){
std::cout << "Function without int as argument. Arg = " << arg << std::endl;
};
// We use std::get to get the function and the call it.
// The the trailing '()' and '(5)' are the actual function calls,
// just like in the example above
std::get<0>(functionTuple)();
std::get<1>(functionTuple)(5);
// You can also use std::get<...> with a type as argument.
// Have a look in the docs. Its a very nice feature of tuples
return 0;
}

如果你想同时实现不同的参数和多个函数,你可以将std::tuplestd::vector:结合起来

#include <iostream>
#include <functional>
#include <tuple>
#include <vector>
int main()
{
std::tuple<
std::vector<std::function<void()>>,
std::vector<std::function<void(int)>>
> functionTuple;
// We use push_back in this example, since we deal with vectors.
std::get<0>(functionTuple).push_back([](){
std::cout << "Function without arguments." << std::endl;
});
std::get<1>(functionTuple).push_back([](int arg){
std::cout << "Function without int as argument. Arg = " << arg << std::endl;
});
std::get<1>(functionTuple).push_back([](int arg){
std::cout << "Another function without int as argument. Arg = " << arg << std::endl;
});
std::get<0>(functionTuple).front()();
int i = 5;
// And we use foreach, to loop over all functions which take one integer as argument
for(auto& f :  std::get<1>(functionTuple)) {
f(i);
i += 5;
}
return 0;
}

尽管如此,我还是要加一句警告。函数指针/对象和lambda只是一个工具。它们非常灵活和强大,正因为如此,你可能会陷入意想不到的行为和错误的兔子洞。如果你不打算编写非常通用的算法并深入到模板元编程中,那么这个工具很可能不是最好的工具。寻求不同的解决方案,比如命令模式,可以让你的生活变得更轻松。

另一种可能性是通过添加一个可以被x的主体忽略的额外int参数来更改"x"的签名以匹配"y"的签名。

简单。将参数放入结构或基类中。

如果使用指向基类的指针,则可以扩展泛型。

一种老式的方法是传递一个void指针,并让函数正确地进行强制转换。

事实上,只要不能将不同类型的不同对象推入向量,就不能将指向不同签名函数的不同指针推入向量。

class A{};
class B{};
A aObj;
B bObj;
std::vector<class A> vecA;
vecA.push_back(aObj); // ok
vecA.push_back(vecB); // error
  • 仅推送与矢量实例所需类型相同的对象:

    #include "stdafx.h"
    #include <iostream>
    #include <vector>
    
    void    Foo()  { std::cout << "Foo()" << std::endl; }
    void    Foo2() { std::cout << "Foo2()" << std::endl; }
    int     Bar(float) { std::cout << "Bar(float)" << std::endl; return 0; }
    double  Baz(int, int) { std::cout << "Baz(int, int)" << std::endl; return 0;    }
    
    int main(){
    std::system("color 1f");
    
    typedef void(*pFunc1)();
    typedef int(*pFunc2)(float);
    typedef double(*pFunc3)(int, int);
    pFunc1 pFn1 = Foo;
    pFunc1 pFn2 = Foo2;
    //pFunc1 pFn3 = Bar; // error here I guess you k now why
    std::vector<pFunc1> pvcFunc1;
    std::vector<pFunc2> pvcFunc2;
    std::vector<pFunc3> pvcFunc3;
    
    pvcFunc1.push_back(pFn1);
    pvcFunc1.push_back(pFn2);
    for (int i(0); i < pvcFunc1.size(); i++) {
    pvcFunc1[i]();
    }
    
    
    std::cout << std::endl << std::endl << std::endl;
    std::cin.get();
    return 0;
    }
    

我不会这么做。我不能告诉你在一个向量中同时使用两个函数是否可能——我很确定不是。

您应该创建一个类并使用对象的向量。