C++ std::function

在现代 C++ 中,std::function(定义在 <functional> 头文件中)是一个通用的多态函数封装器,能够存储、复制和调用任何可调用对象 。你可以把它理解为一个“能够持有任何可调用对象”的容器。

std::function 出现之前,我们无法在一个容器或变量中存储不同类型的“可调用对象”。

  • 普通函数指针只能存函数指针
  • Lambda 的类型在编译期是唯一的匿名类型,甚至连两个写法相同的 Lambda 类型都不同
  • std::function 通过“类型擦除”技术,抹去了具体类型的差异,统一为 Ret(Args...) 的接口

一、定义

std::function 模板实例化了一个可以容纳任何满足以下条件的对象:

  • 函数指针
  • Lambda 表达式
  • 函数对象(仿函数,重载了 operator() 的类)
  • 成员函数指针(配合 std::bind 或 Lambda 使用)
1
2
3
4
template<class R, class... Args>
class function<R(Args...)> {
// 构造函数、赋值、swap、operator bool、operator() 等
};

注意,它要求可调用对象的拷贝是可用的,因为 std::function 通常存储其副本。

二、基本用法示例

2.1 普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <functional>
#include <iostream>

int cook(int a, int b) {
std::cout << a << " + " << b << " = " << a+b << std::endl;
return a + b;
}

int main()
{
std::function<int(int, int)> func = cook; // 函数名自动退化成函数指针
func(3, 5); // output: 3 + 5 = 8

return 0;
}

2.2 函数指针

和普通函数一样:

1
2
3
4
5
6
7
8
9
10
11
#include <functional>
#include <iostream>

int main()
{
int (*funcPtr)(int,int) = cook; // 定义函数指针
std::function<int(int,int)> func2 = funcPtr;
func2(7, 2); // output: 7 + 2 = 9

return 0;
}

2.3 lambda 表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <functional>
#include <iostream>

int main()
{
auto lambda = [](int a, int b) {
std::cout << a << " * " << b << " = " << a * b << std::endl;
return a * b;
};
std::function<int(int, int)> func3 = lambda;
func3(4, 6); // output: 4 * 6 = 24

return 0;
}

2.4 仿函数

只要一个类重载了 operator(),它的实例就是一个函数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <functional>
#include <iostream>

struct Multiplier {
int operator()(int a, int b) const {
std::cout << a << " * " << b << " = " << a * b << std::endl;
return a * b;
}
};

int main()
{
Multiplier mul; // 创建函数对象
std::function<int(int, int)> func4 = mul;
func4(3, 7); // output: 3 * 7 = 21

return 0;
}

2.5 std::bind 产生的绑定表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <functional>
#include <iostream>

int add(int a, int b, int c) { // 三个参数的函数
std::cout << a << " + " << b << " + " << c << " = " << a + b + c << std::endl;
return a + b + c;
}

int main()
{
auto bound = std::bind(add, 10, std::placeholders::_1, 20); // _1用来占位,是将来传入的第一个参数
std::function<int(int)> func5 = bound; // 注意签名变成了 int(int)
func5(5); // output: 10 + 5 + 20 = 35

return 0;
}

std::bind 使用占位符来表示未绑定的参数,这些占位符决定了在生成的新函数对象中如何传递参数。

常见的占位符有:

  • std::placeholders::_1
  • std::placeholders::_2
  • std::placeholders::_3
  • ……

2.6 成员函数指针

成员函数指针不能直接存进 std::function,因为它需要绑到一个对象上才能调用。

常见的包装方式有两种:std::bindlambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <functional>
#include <iostream>

struct Chef {
void cookDinner(int count) const {
std::cout << "count value: " << count << std::endl;
}
};

Chef myChef;

int main()
{
// 1. std::bind 方式
auto boundMember = std::bind(&Chef::cookDinner, &myChef, std::placeholders::_1);
std::function<void(int)> func6 = boundMember;
func6(3); // output: count value: 3

// 2. lambda 方式
std::function<void(int)> func7 = [&myChef](int count) {
myChef.cookDinner(count);
};
func7(5); // output: count value : 5

return 0;
}

2.7 另一 function 对象

std::function 本身也是可拷贝的,所以我们可以把一个 std::function 赋值给另一个(只要签名相同)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <functional>
#include <iostream>

struct Chef {
void cookDinner(int count) const {
std::cout << "count value: " << count << std::endl;
}
};

Chef myChef;

int main()
{
std::function<int(int, int)> original = cook;
std::function<int(int, int)> copy = original; // 拷贝构造
copy(2, 4); // output: 2 + 4 = 6

return 0;
}

三、性能优化

3.1 性能开销

虽然 std::function 非常方便,但它是有代价的:

  1. 堆分配:为了实现类型擦除,std::function 内部通常会动态分配内存(取决于可调用对象的大小,虽然有小对象优化 SSO,但通常开销仍大于普通函数指针)
  2. 间接调用:由于它内部使用了虚函数或函数指针的封装,编译器很难对其进行内联优化,这会带来额外的开销
  3. 体积大:一个 std::function 对象通常占用 32~64 字节,而函数指针仅占用 8 字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <functional>

int main()
{
auto lambda1 = []() {};
std::function<void()> func = lambda1;
std::cout << "lambda1 sizeof: " << sizeof(lambda1) << std::endl;
std::cout << "func sizeof: " << sizeof(func) << std::endl;

return 0;
}

/* output:
lambda1 sizeof: 1
func sizeof: 32
*/

3.2 使用 std::move 进行性能优化

在 C++ 中,std::function 的性能瓶颈主要在于其内存分配对象拷贝。当我们将一个带有大量捕获变量(capture)的 Lambda 赋值给 std::function 时,编译器可能会在堆上分配内存来存储这些捕获的变量。

使用 std::move 来优化 std::function,核心思路是:**避免不必要的深拷贝,并利用移动语义将大对象的所有权转移进去。

移动语义可以让 std::function 在赋值或构造时转移所有权,而不是深拷贝内部资源。

尤其当内部有堆分配时,移动避免了昂贵的堆内存复制,只需交换指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
std::function<int(int)> createBigFun(bool useMove)
{
// 假设这个 lambda 捕获了一个巨大的 vector,导致堆分配
std::vector<int> huge(1000000);

// 在捕获前显式制造一次拷贝
if (useMove) {
return [huge = std::move(huge)](int x) { return x + huge[0]; }; // 捕获初始化,移动捕获
}
else {
return [huge](int x) { return x + huge[0]; }; // 按值捕获,拷贝 huge
}
}

int main() {
auto start = std::chrono::steady_clock::now();
std::function<int(int)> f1 = createBigFun(false); // 进行拷贝
auto end = std::chrono::steady_clock::now();
std::cout << "f1耗时: "
<< std::chrono::duration<double, std::milli>(end - start).count()
<< " ms" << std::endl;

start = std::chrono::steady_clock::now();
std::function<int(int)> f2 = createBigFun(true); // 进行移动
end = std::chrono::steady_clock::now();
std::cout << "f2耗时: "
<< std::chrono::duration<double, std::milli>(end - start).count()
<< " ms" << std::endl;

return 0;
}

/* output:
f1耗时: 5.78551 ms
f2耗时: 3.62218 ms
*/

下面再看一下拷贝构造和移动构造的性能差别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
std::function<int(int)> createBigFun(bool useMove)
{
// 假设这个 lambda 捕获了一个巨大的 vector,导致堆分配
std::vector<int> huge(1000000);

// 在捕获前显式制造一次拷贝
if (useMove)
{
return [huge = std::move(huge)](int x)
{ return x + huge[0]; }; // 捕获初始化,移动捕获
}
else
{
return [huge](int x)
{ return x + huge[0]; }; // 按值捕获,拷贝 huge
}
}

int main()
{
std::function<int(int)> f1 = createBigFun(false);

auto start = std::chrono::steady_clock::now();
std::function<int(int)> f3 = f1; // 拷贝构造 → 深拷贝内部资源
auto end = std::chrono::steady_clock::now();
std::cout << "拷贝耗时: "
<< std::chrono::duration<double, std::milli>(end - start).count()
<< " ms" << std::endl;

start = std::chrono::steady_clock::now();
std::function<int(int)> f4 = std::move(f1); // 移动构造 → 仅转移资源,f1 变为空
end = std::chrono::steady_clock::now();
std::cout << "移动耗时: "
<< std::chrono::duration<double, std::milli>(end - start).count()
<< " ms" << std::endl;

return 0;
}

/* output:
拷贝耗时: 1.25801 ms
移动耗时: 9.6e-05 ms
*/
  • 拷贝:可能触发堆内存的完整复制,开销与对象大小成正比
  • 移动:通常只是交换内部指针,常数时间,而且源对象变为空

3.3 使用场景

  1. std::function 存入容器时:如果我们有一个 vector<std::function<void()>>,并且我们不再需要原始的 function 对象,用 emplace_back(std::move(func)) 或者 push_back(std::move(func)) 可以避免拷贝。
1
2
3
std::vector<std::function<void()>> tasks;
std::function<void()> task = []{ /* 复杂操作 */ };
tasks.push_back(std::move(task)); // 移动,不再拷贝
  1. 作为函数参数传递时(尤其是回调):如果函数接受 std::function 作为参数,并且我们希望将所有权转移进去,用移动可以省去一次拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void enqueue(std::function<void()> task) 
    {
    // 假设这里将 task 存入队列
    queue_.push_back(std::move(task));
    }

    // 调用方:
    std::function<void()> myTask = []{ doWork(); };
    enqueue(std::move(myTask)); // 移动传递
  2. 返回 std::function 时(已经自动优化):现代 C++ 编译器通常会应用 RVO(返回值优化) 或隐式移动。

    当我们返回一个局部 std::function 时,一般不需要显式 std::move,显式写反而可能阻止某些优化。让编译器自己决定最优雅


C++ std::function
http://example.com/2026/05/19/C++-std-function/
作者
Yu xin
发布于
2026年5月19日
许可协议