【问题标题】:modern c++ alternative to function pointers函数指针的现代 C++ 替代方案
【发布时间】:2015-08-25 06:24:55
【问题描述】:

到目前为止,我一直在使用函数指针,就像 c++ 中的这种格式。我确实时不时地有一些用途,我想知道在 c++11/14 中是否还引入了其他任何东西作为它们的替代方案。

#include <iostream>
using namespace std;

void sayHello();
void someFunction(void f());

int main() {

    someFunction(sayHello);
    return 0;
}
void sayHello(){
    std::cout<<"\n Hello World";
}
void someFunction(void f()){
    f();
} 

我确实看过这个question,但无法理解与传统使用函数指针相比​​的任何优势。另外我想问一下,使用函数指针有什么问题(不推荐),因为我从未见过有人使用它们。或任何其他替代方案。

【问题讨论】:

标签: c++ pointers c++11 function-pointers


【解决方案1】:

您提到的问题建议使用 std::function,但在与 std::bind 结合使用时并未强调(或根本没有提及)它的价值。

你的例子是最简单的,但假设你有一个

std::function<void (int, int)> f ;

函数指针可以做或多或少相同的事情。但是假设你需要一个函数 g(int),它是 f,第二个参数绑定为 0。使用函数指针你不能做很多事情,使用 std::function 你可以这样做:

std::function<void(int)> g = std::bind(f, _1, 0) ;

【讨论】:

  • 这看起来很有希望,但我很困惑为什么我要使用另一个函数 g 比 f 少一个参数?换句话说,std::bind 与 std::function 的可能实际用例是什么?
  • @hg_git:假设你有一个compare(string, string, bool caseSensitive)。当您需要不区分大小写的比较时,您可能需要传递 std::bind(compare, _1, _2, false)
  • @hg_git 如果您想要带有延迟参数的函数。想想一个持有“待办事项”列表的工作线程。并且非常常见:带有“撤消列表”的应用程序。将记录每个用户步骤,并将函数调用放入包含参数的列表中(绑定)。如果要撤消,只需向后调用列表并执行与函数相反的操作。
  • @Klaus:与简单的状态快照相比,这种撤消列表作为一种策略已经死了。与 COW 状态相比,为每个可能的用户操作编写“撤消”是很糟糕的,在 COW 状态中您可以将状态恢复为任何操作。
  • @Puppy 这只是一个例子!如果数据非常复杂且庞大,那么存储长撤消列表的每个状态听起来几乎是不可能的。所以我不敢相信“撤消功能”已经死了。但根本不是:这是一个例子:-)
【解决方案2】:

作为传统函数指针的替代方案,C++11 引入了template alias,结合variadic templates 可以简化函数指针sintax。下面是一个如何创建“模板”函数指针的示例:

template <typename R, typename ...ARGS> using function = R(*)(ARGS...);

可以这样使用:

void foo()                { ... }
int bar(int)              { ... }
double baz(double, float) { ... }

int main()
{
    function<void>                  f1 = foo;
    function<int, int>              f2 = bar;
    function<double, double, float> f3 = baz;

    f1(); f2({}); f3({}, {});
    return 0;
}

此外,它还可以巧妙地处理函数重载:

void overloaded(int)      { std::cout << "int version\n"; }
void overloaded(double)   { std::cout << "double version\n"; }

int main()
{
    function<void, int>    f4 = overloaded;
    function<void, double> f5 = overloaded;

    f4({}); // int version
    f5({}); // double version
    return 0;
}

并且可以用作声明函数指针参数的一种非常简洁的方式:

void callCallback(function<int, int> callback, int value)
{
    std::cout << "Calling\n";
    std::cout << "v: " << callback(value) << '\n';
    std::cout << "Called\n";
}

int main()
{
    function<int, int> f2 = bar;
    callCallback(f2, {});
    return 0;
}

此模板别名可以用作std::function 的替代品,它没有缺点也没有优点 (good explanation here)。

Live demo

简而言之,我认为模板别名与可变参数模板相结合是 raw 函数指针的一个很好、漂亮、整洁和现代的 C++ 替代方案(这个别名毕竟仍然是函数指针)但是 @ 987654330@ 是好、好、整洁和现代的 C++ 以及需要考虑的良好优势。坚持使用函数指针(或别名)或选择std::function 取决于您的实现需求。

【讨论】:

  • 不,绝对不是。如果您不想要 std::function 的优势,请使用模板——一个真正的模板,而不仅仅是一个简化语法的别名。
【解决方案3】:

另外我想问一下,有什么问题吗(不推荐) 使用函数指针的事情,因为我从未见过有人使用 他们。

是的。函数指针是可怕的、可怕的东西。首先,它们不支持泛型——所以你不能使用一个函数指针,比如说,将std::vector&lt;T&gt; 用于任何T。其次,他们不支持绑定状态,所以如果在未来的任何时候,任何人想要引用其他状态,他们就完蛋了。这尤其糟糕,因为其中包括用于成员函数的 this

在 C++11 中有两种获取函数的方法。首先是使用模板。第二种是使用std::function。

模板有点像这样:

template<typename T> void func(F f) {
    f();
}

这里的主要优点是它接受任何类型的函数对象,包括函数指针、lambda、仿函数、bind-result 等等,并且 F 可以有任意数量的带有任何签名的函数调用重载,包括模板,并且它可以有任何大小和任何绑定状态。所以它超级灵活。它还可以最大限度地提高效率,因为编译器可以内联运算符并直接在对象中传递状态。

int main() {
    int x = 5;
    func([=] { std::cout << x; });
}

这里的主要缺点是模板的常见缺点——它不适用于虚函数,必须在标题中定义。

另一种方法是std::functionstd::function 有许多相同的优点——它可以是任何大小,绑定到任何状态,并且可以是任何可调用的,但需要权衡一些。主要是,签名在类型定义时是固定的,所以对于一些未知的T,你不能有std::function&lt;void(std::vector&lt;T&gt;)&gt;,并且可能还涉及一些动态间接/分配(如果你可以的话)吨 SBO)。这样做的好处是,由于std::function是一个真正的具体类型,你可以像传递任何其他对象一样传递它,所以它可以用作虚函数参数之类的东西。

基本上,函数指针非常有限,不能真正任何有趣的事情,并使 API 非常不灵活。他们可恶的语法是大海里的小便,用模板别名减少它很有趣但毫无意义。

【讨论】:

  • template&lt;typename T&gt; void func(F f) - 哪个是错字 - T 型还是 F 型?
【解决方案4】:

我确实看过这个问题,但无法理解 与传统使用函数指针相比​​的优势。我也会 想问一下,有什么问题(不推荐)吗 使用函数指针,因为我从未见过有人使用它们。

  • 普通的“全局”函数通常没有/不能有状态。虽然在函数式编程范式中的遍历期间拥有状态不一定很好,但有时当状态与已更改的内容正交相关时(例如启发式),状态可能会派上用场。此处函子(或函数对象)具有优势。

  • 普通函数不能很好地组合(创建低级函数的高级函数。

  • 普通函数不允许动态绑定其他参数。

  • 有时,普通函数可以替代 lambda,反之亦然,具体取决于上下文。通常人们不会仅仅因为在“容器遍历”期间有一些非常本地/特定的需求而想要编写一个特殊的函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多