【问题标题】:How do I create an array of function pointers of different prototypes?如何创建不同原型的函数指针数组?
【发布时间】:2019-05-06 19:34:02
【问题描述】:

我有几个这样定义的函数:

ParentClass*    fun1();
ParentClass*    fun2();
ParentClass*    fun3(bool inp=false);
ChildClass*     fun4();
ChildClass*     fun5(int a=1, int b=3);

我想将它们放入某种数组中,如下所示:

void* (*arr[5])() = {
    (void* (*)())fun1,
    (void* (*)())fun2,
    (void* (*)())fun3,
    (void* (*)())fun4,
    (void* (*)())fun5
}

现在我想简单地使用这个函数数组

for(int i=0; i<5; i++)
    someFunction(arr[i]());

现在我意识到问题是 void* (*arr[5])(),但鉴于我只想使用函数而不提供参数,我希望所有这些都成为同一个数组的一部分。

不过,这些都是非常 C 风格的方法。有没有更好的方法来使用 C++ 中的模板?

【问题讨论】:

  • 为什么要丢弃返回的类型? ChildClass 不继承 ParentClass 吗?
  • 当我使用它时,我实际上将它投射到ParentClass。我可以将其设为 ParentClass* 而不是 void*,但这样我就必须将每个条目类型强制转换为数组。相反,我目前选择在从数组中检索它之后进行转换。
  • 我通过我不想使用的问题传达了错误信息。我确实想使用它。相应地修改了问题。
  • 在你的第一个 sn-p 中你已经声明了两次 fun4,这是一个错字吗?
  • 是的。已更正。

标签: c++ function oop c++14


【解决方案1】:

C 风格与否,你所拥有的是直接未定义的行为。使用 lambda:

void (*arr[5])() = {
    [] { fun1(); },
    [] { fun2(); },
    [] { fun3(); },
    [] { fun4(); },
    [] { fun5(); }
};

这些都可以,因为它们通过函数的正确类型执行调用,并且它们本身可以转换为 void (*)()

转发返回的值非常简单,因为 lambda 为转换提供了上下文。在您的情况下,由于 ChildClass 应该继承自 ParentClass,因此隐式转换就足够了:

ParentClass *(*arr[5])() = {
    []() -> ParentClass * { return fun1(); },
    []() -> ParentClass * { return fun2(); },
    []() -> ParentClass * { return fun3(); },
    []() -> ParentClass * { return fun4(); },
    []() -> ParentClass * { return fun5(); }
};

【讨论】:

  • 这是否使函数的返回类型void
  • @vc669 是的,因为无论如何您都不使用它。但是,您也可以使用 lambdas 来协调返回类型,因为我猜 ChildClass 继承自 ParentClass 只是 []() -&gt; ParentClasss * { return fun1(); }
  • 没有意识到我的使用(或缺乏使用)会有所作为。我实际上确实打算使用它。我已经相应地更新了问题,并按照您的评论解决问题。
  • @Aconcagua 是的,但一致性。可以通过使用一个同时接收所有fun* 并返回包装函数数组的函数来避免重复。
  • @Aconcagua Almost:它被编译成一个jmp。虽然如果适用,函数的主体将内联到 lambda 中。
【解决方案2】:

但鉴于我只想使用函数而不提供参数

它根本不是那样工作的。你有没有想过,当你将函数声明放在头文件中时,为什么必须将默认参数写入头文件,而不能将其放在实现源文件的定义中?

这是因为默认参数实际上并没有“嵌入”到函数中,而是被编译器用来在调用位置使用这些参数来扩充函数调用,而这些参数被省略了。 (编辑:另外,正如@Aconcagua 在评论中如此敏锐地观察到的那样,由于默认参数通常被定义为头函数声明的一部分,因此默认值的任何更改都需要对包含这些头文件的任何编译单元进行完全重新编译, ergo 函数声明,以使更改真正生效!)

虽然完全有可能做一些非常奇怪的类型转换疯狂来构造一个这样的函数指针数组,但最终您必须转换回原始函数调用签名才能不调用未定义的行为。

如果有任何事情,您必须绑定函数指针,以及某种类型的默认参数集,这些参数抽象出调用,确实提供参数,并向外部提供多态接口。所以你会有一个std::vector&lt;function_binder&gt;function_binder[],其中函数绑定器有一个调用函数的operator()

但是当您首先进行绑定时,您可以将其绑定到匿名函数中,即 lambdas。在 lambda 实例化时,默认参数是绑定的。

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); }
};

【讨论】:

  • 可能值得一提的是,由于标头中提供了参数,如果更改这些参数,任何依赖的翻译单元都需要重新编译以使这些更改也生效...跨度>
【解决方案3】:

您可以使用std::bind

std::function<ParentClass *(void)> arr[5] = {
    std::bind(&fun1),
    std::bind(&fun2),
    std::bind(&fun3, false),
    std::bind(&fun4),
    std::bind(&fun5, 1, 3)
};

现在可以了

for(int i=0; i<5; i++)
    arr[i]();

你必须确保所有函数的每个函数参数都是绑定的。

这也适用于成员函数。您只需要将对象引用(例如this)绑定为第一个参数。

【讨论】:

  • 因为 C++11 及以上版本没有必要使用 bind,因为 lambda 更方便、更快。
  • @Marek R - 根据实现 std::bind 可能只返回一个 lambda,因为它的返回类型未指定。使用哪一种可能只是口味问题。
  • 我的口味是std::bind,因为如果迫使缺乏纪律的开发人员编写小函数。 Lambda 经常被过度使用并成长为可笑的大小,但它仍然更易于使用且速度更快。 std::bind 创建了复杂的模板,这在分析崩溃日志时很烦人。
  • @Quentin - 谢谢。当然你不能有auto 的数组。我更正了。
  • @Detonar 现在是因为std::function 对这份工作来说太过分了:p
【解决方案4】:

解决方案:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<auto f, class R, class...Args>
struct explicit_function_caster {
  using Sig=R(Args...);
  using pSig=Sig*;
  constexpr operator pSig()const {
    return [](Args...args)->R {
      return static_cast<R>(f(std::forward<Args>(args)...));
    };
  }
};

template<auto f>
struct overload_storer_t {
  template<class R, class...Args>
  constexpr (*operator R() const)(Args...) const {
    return explicit_function_caster<f, R, Args...>{};
  }
  template<class...Args>
  auto operator()(Args&&...args)
  RETURNS( f( std::forward<Args>(args)... ) )
};
template<auto f>
overload_storer_t<f> generate_overloads={};

#define OVERLOADS_OF(...) \
  generate_overloads< \
    [](auto&&...args) \
    RETURNS( __VA_ARGS__( decltype(args)(args)... ) ) \
  >

这是很多样板,但让我们明白:

ParentClass* (*arr[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};
void (*arr2[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};

基本上generate_overloads&lt;x&gt; 采用constexpr 可调用对象x 并允许您在编译时将其转换为指向任何兼容签名的函数的指针,并使用(几乎)任何签名调用它。

同时,OVERLOADS_OF 将函数名称转换为 constexpr 对象,该对象对该函数名称进行重载决策。我在这里使用它是因为 fun3 作为函数指针不知道它的默认参数,但在重载解析时它知道。

在这种特殊情况下,编写玩具 lambda 来完成这项工作要容易得多;这只是尝试为任意兼容的签名自动编写那些玩具 lambda。

【讨论】:

  • constexpr (*operator R() const)(Args...) const 哦,这到底是什么......另外,decltype(args)(args)...std::forward 的替代品还是有其他微妙之处?
  • @Quentin 是的。我没有args 的类型。强制转换运算符在那里是因为 C++2a 编译器不喜欢基于 auto 的部分特化,所以我无法轻松解压缩 Sig:这是一个从类型推断到函数指针的转换运算符。
  • @Quentin Laugh,我想我在 const 方法中转换为 const 函数指针。意外:函数指针是 const 的事实毫无意义。
【解决方案5】:

既然你已经用 C++14 标记了问题,你不应该使用函数指针!

对于 C++14,您应该更喜欢 std::function 和 lambdas。

此外,您不应使用 C 样式数组,而应仅使用 std::array 和/或 std::vector

同时避免使用原始指针,使用std::unique_ptrstd::shared_ptr

所以最简单和最好的解决方法是:

std::array<std::function<ParentClass*()>,5> arr {
    []() { return fun1(); },
    []() { return fun2(); },
    []() { return fun3(true); },
    []() { return fun4(); },
    []() { return fun5(7, 9); }
};

为什么不像@Quentin 回答那样简单的指针数组?他使用了 lambda,但他不能使用绑定任何东西的 lambda(如果需要的话)。

【讨论】:

  • 投反对票,因为std::function 是一个重量级的工具,并且手头的问题不需要它提供的任何功能,而不是普通函数指针。
猜你喜欢
  • 2023-03-12
  • 2021-07-02
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 2016-11-18
相关资源
最近更新 更多