std::function的解释

14
< p > std::function的目的是什么?据我所知,std::function将函数、函数对象或lambda表达式转换为函数对象。

我不太明白这个的目的...Lambda表达式和函数对象已经是函数对象了,我相信它们可以用作算法(如sort和transform)的谓词。顺便说一句,Lambda实际上是函数对象(内部)。因此,我只能看到std::function有用之处是将常规函数转换为函数对象。

而且我不太明白为什么要将常规函数转换为函数对象。如果我想使用函数对象,我会首先创建一个作为函数对象或lambda的函数...而不是编写一个函数,然后使用std :: function将其转换并将其作为谓词传递...

我猜std::function还有更多含义...在第一眼看起来不太明显。

我希望能够解释一下std::function


1
因为你无法创建一个lambda的向量。 - Axalo
2
每当需要擦除函数对象的类型时,就需要使用它。在先前评论中提到的lambda向量就是一个例子。 - Praetorian
观察者模式对象是一个非常适合进行类型擦除的例子,例如,如果您需要存储“回调”或“回调”向量以在某些条件触发时稍后调用。 - Daniel Schepler
@Praetorian 这是“多则少”的情况。类型擦除是面向对象编程中最基本、最容易被忽视和遗忘的特性。 - Swift - Friday Pie
感谢大家清晰的解释! - code
6个回答

14

什么是std::function的目的?据我所知,std::function将函数、仿函数或lambda转换为函数对象。

std::function是类型抹除的一个例子。你所描述的并不完全准确。以选择一个特定的实例化类型std::function<void()>来说明,它表示可使用零参数调用的任何可调用对象。它既可以是函数指针或具有具体类型的函数对象,也可以是由lambda构建的闭包。源类型是什么并不重要,只要符合契约-它就能工作。我们"抹除"了具体的源类型,只处理std::function

那么,为什么我们要使用类型抹除呢?毕竟,我们难道没有模板可以直接使用具体类型吗?这样做不会更有效率吗?而且C++不是一切都关乎效率吗?!

有时候,您不能使用具体类型。一个更为常见的示例是常规面向对象的多态性。为什么我们要存储 Base* 而不是存储 Derived* 呢?可能我们无法存储 Derived*。可能我们有很多不同的 Derived*,供不同的用户使用。或许我们正在编写一个甚至不知道 Derived 的库。这也是类型抹消,只是与 std::function 使用的技术不同而已。
一些用例(非穷尽列表):
- 需要存储潜在异构对象的列表,当我们只关心它们是否满足某个具体接口。对于 std::function,也许我只有一个 std::vector<std::function<void()>> callbacks,其中可能有各种不同的具体类型,但我并不关心,只需要调用它们即可。 - 需要跨 API 边界使用(例如,我可以有一个使用 std::function<void()> 的虚函数,但我不能有一个虚函数模板)。 - 从工厂函数返回 - 我们只需要满足某个概念的对象,我们不需要具体的东西(再次强调,在 OO 多态中也很常见,这也是类型抹消)。 - 可能实际上可以在任何地方都使用模板,但性能提升不值得编译时间的损失。

7
考虑一个简单的使用案例:
/* Unspecified */ f = [](int x, int y){ return x + y; };
f = [](int x, int y){ return x - y; };
int a = 42;
f = [&a](int x, int y){ return a * x * y; };

您如何指定/* 未指定 */

此外,

std::queue<of what?> jobs;
jobs.push_back([]{ std::cout << "Hi!\n"; });
jobs.push_back([]{ std::cout << "Bye!\n"; });
for(auto const &j: jobs) j();

jobs中应该保留什么value_type

最后,

myButton.onClick(f);
< p > f 是什么类型?一个模板参数吗?好的,但是它在内部是如何注册的呢?< /p >

2
在我看来,大多数情况下,std::function是过度设计了。但它有两个作用。
首先,它为调用函数对象提供了统一的语法。例如,您可以使用std::function实例来包装一个普通函数,该函数接受一个类类型的单个参数或成员函数以及应该应用它的类对象,而不必担心不同的调用语法。
struct S {
    void f();
};

void g(const S&);

S obj;

typedef std::function<void()> functor1(&S::f, obj);
typedef std::function<void()> functor2(&g, obj);

functor1(); // calls obj.f()
functor2(); // calls g(obj);

请注意这里的两个函数对象都使用相同的语法进行调用。这是在编写通用代码时的一个巨大优势。如何调用底层函数的决定是在std::function模板内部进行的,您不必在自己的代码中解决它。
另一个重要的优点是您可以重新分配std::function对象所持有的函数对象:
functor1 = std::function<void>()>(&g, obj);

这将改变functor1的行为:

functor1() // calls g(obj)

有时这很重要。

1
一个使用 std::function 很有用的例子是实现“观察者模式”。例如,假设您想要实现一个简单的“表达式求值器”计算器 GUI。为了给出一些抽象的代码示例,您可能会使用观察者模式针对 GUI 库编写以下代码:
class ExprEvalForm : public GuiEditorGenerated::ExprEvalForm {
public:
    ExprEvalForm() {
        calculateButton.onClicked([] {
            auto exprStr = exprInputBox.get();
            auto value = ExprEvaluator::evaluate(exprStr);
            evalOutputLabel.set(std::to_string(value));
        });
    }
};

现在,GUI库的按钮类如何存储传递给 onClicked 的函数?即使使用模板,onClicked 方法仍然需要将函数存储到成员变量中,并且该变量需要是预定类型。这正是 std::function 的类型抹消发挥作用的地方。因此,按钮类实现的框架可能如下所示:
class PushButton : public Widget {
public:
    using ButtonClickedCallback = std::function<void()>;
    void onClicked(ButtonClickedCallback cb) {
        m_buttonClickedCallback = std::move(cb);
    }

protected:
    void mouseUpEvent(int x, int y) override {
        ...
        if (mouseWasInButtonArea(x, y))
            notifyClicked();
        ...
    }

private:
    void notifyClicked() {
        if (m_buttonClickedCallback)
            m_buttonClickedCallback();
    }
    ButtonClickedCallback m_buttonClickedCallback;
};

1
据我所知,std::function可以将函数、仿函数或者lambda表达式转换成函数对象。
你总结得非常好,你可以将任何一个这些转换成相同的东西,即std::function,然后按照你的意愿存储和使用。
当你设计一个类或者API时,通常没有理由限制你的特性只针对其中一种,因此使用std::function给API用户提供了选择自由,而不是强制用户使用特定类型。你甚至可以将不同形式的这些存储在一起,它基本上是具有给定签名和明确定义语义的可调用类型的抽象。

0

使用函数对象在实现线程池时非常有帮助。您可以将可用的工作线程数保留为线程并将要执行的工作保留为函数对象队列。与函数指针相比,将要执行的工作保留为函数对象更容易,例如您可以传递任何可调用的内容。每次新的函数对象出现在队列中时,工作线程可以弹出它并通过对其调用()运算符来执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接