std :: function的说明

时间:2018-08-20 16:21:15

标签: c++ c++11

std::function的目的是什么?据我了解,std::function将函数,函子或lambda转换为函数对象。

我不太了解这个目的……Lambda和Functors都已经是函数对象,我相信它们可以用作排序和转换等算法的谓词。附带说明一下,Lambda实际上是(内部)函子。因此,我唯一能看到std::function有用的是将常规函数变成函数对象。

我还不太清楚为什么我也想将常规函数变成函数对象。如果我想使用一个函数对象,那么我首先会做一个函子或lambda ...而不是编写一个函数,然后用std :: function对其进行转换,然后将其作为谓词传递给<... / p>

我猜想std::function还有很多……乍一看并不十分明显。

std::function的解释将不胜感激。

6 个答案:

答案 0 :(得分:8)

  

std::function的目的是什么?据我了解,std::function将函数,函子或lambda转换为函数对象。

std::function是称为“类型擦除”的更广泛概念的示例。您的描述不太准确。 std::function<void()>做的是选择一个特定的专业,它表示 any 可调用对象,这些对象可以不带参数地调用。它可以是具有具体类型的函数指针或函数对象,也可以是根据lambda构建的闭包。只要符合合同,源类型是什么都没关系-它就可以工作。我们不使用具体的源类型,而是“擦除”它,而只处理std::function

现在,为什么我们要使用类型擦除?毕竟,我们没有模板以便可以直接使用具体类型吗?那不是更有效吗,不是C ++就是效率吗?

有时,您不能使用具体类型。可能更熟悉的示例是常规的面向对象的多态性。为什么当我们可以存储Base*时为什么还要存储Derived*?好吧,也许我们不能存储Derived*。也许我们有很多不同的Derived*供不同的用户使用。也许我们正在编写一个甚至不了解Derived的库。这也是 类型的擦除,与std::function使用的擦除技术只是一种不同的技术。

用例的详尽清单:

  • 当我们只关心满足特定接口的对象时,需要存储潜在的异构对象列表。对于std::function,也许我只有一个std::vector<std::function<void()>> callbacks-可能都有不同的具体类型,但是我不在乎,我只需要调用它们即可。
  • 需要跨API边界使用(例如,我可以使用一个virtual的{​​{1}}函数,但是我不能使用std::function<void()>函数模板)。
  • 从工厂函数返回-我们只需要一个满足某些概念的对象,就不需要一个具体的东西(同样,在OO多态中很常见,这也是类型擦除)。
  • 实际上可以在任何地方使用模板,但是性能提升不值得编译命中。

答案 1 :(得分:4)

考虑一个简单的用例:

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

您如何指定/* Unspecified */

此外,

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();

value_type中应保留什么jobs

最后,

myButton.onClick(f);

f有什么类型?模板参数?好的,但是如何在内部注册?

答案 2 :(得分:1)

  

据我了解,std :: function将函数,函子或lambda转换为函数对象。

您几乎可以总结一下,您可以将其中的任何转换为std::function,然后可以根据需要存储和使用。

通常,在设计类或API时,通常没有理由将功能限制为其中之一,因此使用std::function可以让API用户自由选择,而不是将用户强制为一种特定类型。 您甚至可以将它们的不同形式存储在一起,这基本上是具有给定签名和明确定义的语义的可调用类型的抽象。

答案 3 :(得分:1)

在我所见过的大多数用途中,std::function过于矫kill过正。但这有两个目的。

首先,它为您提供了用于调用函数对象的统一语法。例如,您可以使用std::function实例化包装一个普通函数,该普通函数采用一个类类型或成员函数的单个参数以及应应用该类的类对象,而不必担心调用语法的不同。 / p>

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)

有时很重要。

答案 4 :(得分: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;
};

答案 5 :(得分:0)

在实现线程池时,使用函数对象很有帮助。您不能将任何可用的工作程序保留为线程,而可以将其作为功能对象的队列进行工作。例如,使工作作为函数对象比函数指针容易完成,因为您可以传递任何可调用的东西。每当新功能对象出现在队列中时,工作线程就可以弹出它并通过在其上调用()运算符来执行。