std::function是用来干什么的,我该如何使用它?

133
我需要使用std::function,但我不知道以下语法的含义。
std::function<void()> f_name = []() { FNAME(); };

使用std::function的目标是什么?是为了创建一个指向函数的指针吗?

2
我认为这里的主要问题不是关于std::function,而是关于lambda表达式。 - chris
68
也许阅读一些文档会有所帮助?-- 我已经阅读过了,但是不理解。如果有一个可以寻求帮助的地方就好了... - Daniel
3个回答

225

std::function是一种类型擦除对象。这意味着它擦除了某些操作发生的细节,并为它们提供了统一的运行时接口。对于std::function来说,主要的操作是复制/移动、销毁和使用operator()进行'调用',也就是'函数式调用运算符'。

用更简单的英语来说,这意味着std::function可以包含几乎任何像函数指针一样调用的对象。

它支持的签名放在尖括号内:std::function<void()>不带参数并且不返回任何值。std::function< double( int, int ) >带有两个int参数并返回double。一般来说,std::function支持存储任何类似函数的对象,其参数可以从其参数列表进行转换,返回值可以转换为其返回值。

重要的是要知道std::function和lambda表达式是不同的,尽管它们是兼容的。

下一部分是一个lambda表达式。这是C++11中的新语法,用于添加编写简单的类似函数的对象的能力 - 可以使用()调用的对象。这样的对象可以进行类型擦除并存储在std::function中,但会带来一些运行时开销。
特别地,[](){ code }是一个非常简单的lambda表达式。它对应于以下内容:
struct some_anonymous_type {
  some_anonymous_type() {}
  void operator()const{
    code
  }
};

上述简单伪函数类型的一个实例。类似上述的实际类是由编译器“创造”的,具有一个实现定义的唯一名称(通常包含用户定义类型不可能包含的符号)(我不知道是否可能在不创造这样一个类的情况下遵循标准,但我所知的每个编译器实际上都会创建这个类)。
完整的lambda语法(在C++20之前)如下:
[ capture_list ]( argument_list )
-> return_type optional_mutable
{
  code
}

到了 ,语法扩展为:
[ capture_list ]
< template_params > requires_clauses
attributes ( argument_list )
-> return_type
{
  body
}

但是很多部分可以省略或留空。capture_list对应于结果匿名类型的构造函数和成员变量,argument_list对应于operator()的参数,而return type对应于返回类型。当使用capture_list创建实例时,lambda实例的构造函数也会被神奇地调用。
[ capture_list ]( argument_list ) -> return_type { code }

基本上变成了
struct some_anonymous_type {
  // capture_list turned into member variables
  some_anonymous_type( /* capture_list turned into arguments */ ):
    /* member variables initialized */
  {}
  return_type operator()( argument_list ) const {
    code
  }
};

请注意,在C++20中,模板参数被添加到lambda函数中,上面的内容没有涵盖到这一点。
[]<typename T>( std::vector<T> const& v ) { return v.size(); }

并且属性和要求子句也可以与lambda一起使用。

[]之后的模板参数适用于operator()而不是整个lambda。即:

template<class T>
auto foo = [](T t) { return t+1; };

是一个模板 变量,而
auto foo = []<class T>(T t) { return t+1; };

是一个带有template operator()的变量。
此外,RTTI(运行时类型信息)也被存储(typeid),并且包含了将对象转回原始类型的操作。

107
用通俗易懂的英语,人们不会使用"abstruse"这个单词。我不得不查阅它的定义:https://www.wordreference.com/definition/abstruse。它的意思是“难以理解”。因此,单词"abstruse"相当难懂。 - Gabriel Staples
@yakk-adam-nevraumont 很好的回答。我还是有点困惑:std::function 的一个用途似乎是声明可调用表达式的类型(返回值+输入参数)。在这种情况下,“auto”似乎是更好的选择?至少打字更少(std::function…有点啰嗦) - torez233
@torez233 人们用它来实现那个目的。但是它并不是很适合,忽略冗长性。它意味着类型擦除,包括状态的复制。 - Yakk - Adam Nevraumont
你的意思是“abstruse”是自指的。 - undefined

89

让我们分开看这行代码:

std::function

这是一个声明,用于声明一个不带参数且不返回值的函数。如果该函数返回一个 int 类型的值,代码应该如下所示:

std::function<int()>
同样地,如果它还需要一个整型参数:
std::function<int(int)>

我猜你最大的困惑在于接下来的部分。

[]() { FNAME(); };

[] 部分被称为 捕获子句。在这里,您可以放置对于 lambda 声明局部的变量,并且您希望这些变量在 lambda 函数内部可用。如果这是在类定义内部,并且您想让该类对 lambda 可用,您可能会这样做:

[this]() { FNAME(); };

下一个部分是传递给Lambda表达式的参数,与普通函数完全相同。正如之前提到的,std::function<void()>是指向不带参数的方法的签名,因此此处也为空。

剩下的部分是Lambda表达式本身的主体,就像它是一个普通函数一样,我们可以看到它只调用了函数FNAME

另一个例子

假设你有以下签名,用于对两个数字求和。

std::function<int(int, int)> sumFunc;
我们现在可以这样声明一个 lambda:
sumFunc = [](int a, int b) { return a + b; };

不确定您是否使用MSVC,但无论如何这里都有一个关于lambda表达式语法的链接:

http://msdn.microsoft.com/en-us/library/dd293603.aspx


6
[](int a, int b) { return a + b; }; 是一个 lambda 函数,它接受两个 int 类型的参数,并返回它们的和。[int a, int b]() { return a + b; }; 也是一个 lambda 函数,但它没有参数列表。它通过捕获 [int a, int b] 引入了两个局部变量 ab,并返回它们的和。因此,两个 lambda 函数都执行相同的操作,但方式略有不同:第一个 lambda 函数接受参数,而第二个 lambda 函数通过捕获引入变量。 - user2982229
21
@user2982229 lots. 其中一个从本地范围内获取 ab,另一个则以 ab 作为参数进行调用。 - Yakk - Adam Nevraumont
1
非常有帮助。我查看了几个资源,这是唯一一个如此直截了当地说明“这种声明意味着这个”的资源。 - jon_simon
我对这个语法有些困惑 - std::function<int(int)>....通常,模板参数是逗号分隔的,就像任何函数的参数一样...但是在这里,却有一个括号?虽然它非常易读且一眼就能看出函数的签名,但是这种语法仍然让人感到困惑。你如何解释它? - NightFuryLxD
1
@NightFuryLxD:std::function只有一个模板参数,因此它没有逗号。一个模板参数必须是一个函数类型。在C++中,ReturnType(Arg1, Arg2)的语法就是你如何写一个函数类型。 - MSalters
1
@MSalters,根据CppReference - template< class R, class... Args >,它需要(1 + n)个参数,第一个是返回类型,然后是n个参数。我原以为我们应该像这样实例化它 - std::function<int, int, int>(需要2个int并返回一个int)。但我不知道您也可以像这样实例化它 - std::function<int(int, int)>。CppReference中也提到了同样的内容 - class function<R(Args...)>; - NightFuryLxD

5

带捕获的Lambda表达式(有状态的Lambda)由于具有唯一的类型,即使它们看起来完全相同,也不能互相赋值。

为了能够存储和传递带捕获的Lambda表达式,我们可以使用“std::function”来保存由Lambda表达式构造的函数对象。

基本上,“std::function”是为了能够将具有不同内容结构的Lambda函数分配给一个Lambda函数对象。

示例:

auto func = [](int a){
 cout << "a:" << a << endl;
};
func(40);
//
int x = 10;
func = [x](int a){ //ATTENTION(ERROR!): assigning a new structure to the same object
 cout << "x:" << x << ",a:" << a << endl;
};
func(2);

因此,上述用法是不正确的。 但是如果我们使用“std :: function”定义函数对象:

auto func = std::function<void(int)>{};
func = [](int a){
  cout << "a:" << a << endl;
};
func(40);
//
int x = 10;
func = [x](int a){ //CORRECT. because of std::function
  //...
}; 

int y = 11;
func = [x,y](int a){ //CORRECT
 //...
}; 

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