【问题标题】:How do you create a vector of function pointers that can take different arguments?如何创建一个可以接受不同参数的函数指针向量?
【发布时间】:2017-08-24 18:33:10
【问题描述】:

我正在尝试学习如何在 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 个。

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

【问题讨论】:

  • 希望以后如何使用您的向量?谁会记住/检查 v[0] 应该用 1 arg 调用,而 v[42] 应该用 2 调用?
  • 如果你想要不同的签名,使用不同的容器。
  • 我用示例代码添加了一个答案,如何实现你想要的。如果您愿意,这种方法取决于您。

标签: c++ vector


【解决方案1】:

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

编写一个增强的变体(std 或 boost 或手动标记的类型安全联合),如果您输入错误,则支持隐式强制转换(如果需要,请随时支持类型之间的转换)。打电话给poly_arg&lt;Ts...&gt;

编写一个将 arg 类型作为模板参数的类型橡皮擦。然后,它在构造时获取一个函数指针,然后使用具有正确长度参数的向量进行类型擦除调用它。 (或一个函数对象和一个 arg 计数范围)。然后它有一个 vararg operator() 将其参数转发到其 arg 类型的向量中,然后尝试使用上述类型擦除来调用。如果传递了错误数量的参数,则会引发异常。打电话给vararg_func&lt;T&gt;

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

如果您只需要支持int args,您可以跳过poly_arg 并改为存储vararg_func&lt;int&gt;

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

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

相反,此计划符合您的要求,这会在其界面中强制类型不安全,并使用“不知道,也许它会起作用”调用污染您的代码。

如果您需要帮助实现这些类型的橡皮擦,请意识到我都知道如何编写它们以及它们如何解决您的问题,并且在我看来它们是一个非常糟糕的主意。如果这不能阻止您,请去了解 C++ 中的类型擦除和值类型多态性以及 std::function 的工作原理。试着写一个玩具std::function。玩“仅查看”和“仅移动”版本。尝试使用有界函数对象大小进行零分配。这应该需要几周或几年的时间。

现在写一些更简单的例子,比如打印到 ostream。做得足够好。此时vararg_func 应该具有挑战性但可行;尝试一下。如果失败,请 SO 提供帮助,包括您的尝试。

poly_arg 应该比较容易。

【讨论】:

  • 你的回答太美了,让我泪流满面。
  • 接受挑战
  • 确实是个糟糕的计划。
【解决方案2】:

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

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

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

【讨论】:

  • 使用void 指针作为函数参数怎么样。这就是 Windows 允许将数据传递给线程的方式。
  • @ThomasMatthews:使用void* 做什么?您仍然需要知道将其转换回哪种类型。
【解决方案3】:

如果您可以访问 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 正确的变体。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

          首先,我建议在函数指针上使用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 中存储具有不同参数的函数的问题。由于它们具有不同的签名,因此您必须将它们视为不同的类型。喜欢intstd::string

          为了存储不同类型的元素,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 只是一种工具。它们非常灵活和强大,因此可能会导致您陷入意外行为和错误的兔子洞。如果您不打算编写非常通用的算法并深入研究模板元编程,那么这个工具很可能不是完成这项工作的最佳选择。寻求不同的解决方案,例如 command pattern,可以让您的生活更轻松。

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-12-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-06-06
            • 1970-01-01
            相关资源
            最近更新 更多