std :: function的目的是什么?

use*_*710 -3 c++ c++11 c++14

我在推理如何编写和设计几个应该处理特定文件格式的函数,这些函数可能有不同的实现和不同的版本,每个函数需要一种不同的方法来解码文件中的这些信息.

我像往常一样浏览标准库,我得到了一个std::function存在的余数,但问题是我无法弄清楚为什么我可能有兴趣使用std::function,在C和C++编程时的第一个规则之一是如果你不必引用你不一定要命名的东西,你可以获得未命名的数据结构和未命名/ lambda函数,但函数通常有一个名称,而lambda的类型仍然是实现根据我的记忆定义,那么需要std::function什么?

例如,在我的情况下,我正在考虑使用a map或an hash table(但所涉​​及的函数数量实际上很小,现在最多2-3个),其中一对由tag(表示文件格式的版本/实现)+ functions,我想知道为什么我不能只使用std::string和函数指针作为每对的2种类型; 我也无法理解为什么我们std::function在标准库中.

我的意思是,当你需要一个未命名的函数并且你需要一个对象来描述它的状态时呢?

rwo*_*ong 6

问题已得到多次回答,答案来自不同的角度.

(按升序问题编号排序.)

我的猜测是,std::function还有什么能让lambda使用方便.

基本上,当你需要完成小事情时,仿函数更方便.当事物超出"小"规模时,接口(抽象基类,或C++中的ABC)更适合.

进一步来说:

  • 当你需要实行"一个班轮"回调函数,接受一个std::function实施一个拉姆达.
  • 这种"一线"产生于:
    • "一班车".说够了.
    • 胶水代码,您需要稍微调整呼叫签名以使呼叫通过.使用std::bind沿着std::function和lambda.
  • 它可以帮助您避免:
    1. 用这些样板创建一个接口(抽象基类,ABC),
    2. 为一个简单的虚拟析构函数提供一个空体(根据某些C++编译器的要求),
    3. 创建一个继承自ABC的类,
    4. 为"捕获的数据"(闭包)创建一个构造函数并将它们存储在私有字段中,
    5. 最后你可以编写代码.
  • 使用lambda,#1进入std::function签名,跳过#2和#3,将#4放在方括号内的逗号分隔列表中,并将#5放在花括号内.auto自由使用.

任何超越"单线程""一个回调函数"的东西都应该有一个接口(ABC).

此外,如果您的代码库未编译为单个部分(即库和链接的分离),则库上公开的任何回调函数最好需要使用接口(ABC).根据您的编译器和链接方法,这可能是也可能不是问题.


Jon*_*ely 6

std::function是基于较旧的boost::function,具有良好的文档,介绍说:

通常,在函数指针用于推迟调用或进行回调的任何地方,可以使用Boost.Function来允许用户在目标实现方面具有更大的灵活性.目标可以是任何"兼容"的函数对象(或函数指针),这意味着Boost.Function指定的接口的参数可以转换为目标函数对象的参数.

N1402提案添加std::tr1::function也有描述为特征的动机的部分.它被添加到标准中,因为数千人已经发现boost::function有用多年.

如果您仍然不明白为什么它有用,您可能无法理解函数指针或回调在哪里有用,所以请查看.

  • 我认为你的想法不够广泛.我上面引用的函数指针的比较来自`std :: function`的作者,也许他们比你更了解它?使用类型擦除是一个实现细节,它的一个重点是函数指针的更通用版本,它可以存储任何形式的可调用对象.它必须使用类型擦除来抽象出`int(*)()`和`struct F {int operator()const之间的区别; };`和`[] {return 1; 但是,类型擦除不是它的原因,而是它如何实现其目的. (4认同)
  • @rwong,这是一个用途,但不是唯一的用途.`std :: function`有很多用法是无状态的,而且很多都是有状态的而不使用`shared_ptr`(例如你可以使用`std :: bind`或lambdas来捕获状态). (2认同)

Yak*_*ont 5

std::function<R(Args...)>type 擦除copydestroy使用 Args... 调用并返回 R,并实现move

从基本回调开始——由调用者提供R(*)(Args...)Args...现在,一个好的回调会将 avoid*传递给调用者,然后他们传回 - 现在您已经有了R(*)(void*,Args...). 接下来,我们需要分离并回收void*回调数据——所以我们有 a void*、 aR(*)(void*,Args...)void(*)(void*)清理。接下来,我们需要值语义——因此调用者还传递如何克隆——void*一个void*(*)(void*)克隆函数(值语义真的很棒)。

现在您有了一个 C 风格版本的std::function<R(Args...)>. 在构造函数中自动std::function生成上述内容,典型的实现存储一个抽象类的接口,该抽象类为传入的可调用类型实现上述内容。

因此,类型擦除允许您采用支持上述类型擦除概念的任何值并将它们存储在统一类型中 - std::function<R(Args...)>

这使您可以接受任何符合您需求的内容,并与其进行统一交互。您可以将其存储在公共类中,可以将其传递给动态加载的库,或者传递给不直接依赖于函数和状态的原始选择的代码块。

您可以使用 C 风格的版本,但将其包装起来会很尴尬。

有一些变体std::function消除了复制概念,有些变体消除了破坏(即不拥有),因为即使没有抽象调用也是值得的。