C++ 从入门到精通之十七

大纲

C++ 进阶

可调用对象

函数指针

核心概念

C++ 中的函数指针是一种指向函数的指针变量,它存储的是函数在内存中的入口地址,而非数据。声明函数指针时需要指定其所指函数的返回类型和参数列表,例如 int (*p)(int, int) 表示一个指向 “接受两个 int 并返回 int“ 的函数的指针。使用时可将函数名(会隐式转换为函数地址)赋给函数指针,然后通过 p(args) 或 (*p)(args) 两种等价方式调用该函数,也可将函数指针作为参数传递给其他函数,从而实现回调机制和运行时动态选择函数的功能。函数指针的局限在于它只能指向具有相同签名的普通函数或静态成员函数,无法直接携带状态,也不能指向非静态成员函数(后者需要成员函数指针且调用时须提供对象),因此在现代 C++ 中常被函数对象、Lambda 表达式和 std::function 等更灵活的机制所替代,但在与 C 语言接口交互、底层系统编程以及需要极轻量级回调的场合仍然广泛使用。

提示

在 C++ 模板类型推导中,值类型推导会忽略引用和顶层 cv;引用类型推导可以保留原始类型;指针类型推导需要区分顶层和底层 const;万能引用根据左值、右值进行不同推导;数组和函数按值传递时会分别退化为指针,而引用传递时可以保留原始类型。

使用案例

案例一:函数指针的使用

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
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <map>
#include <string>

// 定义几个普通函数
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

int multiply(int a, int b) {
return a * b;
}

// 接受函数指针作为参数的函数(回调函数)
int compute(int x, int y, int (*op)(int, int)) {
return op(x, y);
}

int main() {
// 1. 声明并初始化函数指针
int (*p)(int, int) = add;

// 2. 通过函数指针调用
std::cout << "p(3, 4) = " << p(3, 4) << std::endl;
std::cout << "(*p)(3, 4) = " << (*p)(3, 4) << std::endl;

// 3. 让函数指针指向另一个函数
p = subtract;
std::cout << "p(10, 4) = " << p(10, 4) << std::endl;

// 4. 将函数指针作为参数传递(回调)
std::cout << "compute(5, 6, add) = " << compute(5, 6, add) << std::endl;
std::cout << "compute(5, 6, multiply) = " << compute(5, 6, multiply) << std::endl;

// 5. 用函数指针数组实现简单的 "分派表"
int (*ops[])(int, int) = {add, subtract, multiply};
for (int i = 0; i < 3; ++i) {
std::cout << "ops[" << i << "](8, 2) = " << ops[i](8, 2) << std::endl;
}

// 6. 将多个函数指针放入同一个容器
std::map<std::string, int (*)(int, int)> container;
container.insert(std::make_pair("add", ops[0]));
container.insert(std::make_pair("subtract", ops[1]));
container.insert(std::make_pair("multiply", ops[2]));
for (auto it = container.begin(); it != container.end(); ++it) {
std::cout << it->first << "(3, 4) = " << it->second(3, 4) << std::endl;
}

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
p(3, 4) = 7
(*p)(3, 4) = 7
p(10, 4) = 6
compute(5, 6, add) = 11
compute(5, 6, multiply) = 30
ops[0](8, 2) = 10
ops[1](8, 2) = 6
ops[2](8, 2) = 16
add(3, 4) = 7
multiply(3, 4) = 12
subtract(3, 4) = -1

案例二:类成员函数指针的使用

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
#include <iostream>

class MyClass {
public:
// 构造函数
MyClass(const int i) : m_i(i) {
}

// 普通成员函数
void process(int num) {
std::cout << "m_i = " << m_i << std::endl;
std::cout << "num = " << num << std::endl;
}

private:
int m_i = 0; // 私有成员变量,默认初始化为 0
};

int main() {
MyClass mc(5); // 创建 MyClass 对象

// 定义指向 MyClass 成员函数的指针
// 类型为 void (MyClass::*)(int),即 "指向 MyClass 中参数为 int、返回值为 void 的成员函数" 的指针
// 这里让它指向 &MyClass::process
void (MyClass::*fpointer)(int) = &MyClass::process;

// 通过对象 mc 和成员函数指针 fpointer 调用成员函数
// (mc.*fpointer) 表示通过对象 mc 解引用成员函数指针,后面的 (5) 是传给 process() 的参数
(mc.*fpointer)(10);

return 0;
}

程序运行的结果如下:

1
2
m_i = 5
num = 10

函数对象

核心概念

C++ 中的函数调用运算符 operator() 可以被重载,使得类的对象能够像普通函数一样通过 对象名(参数列表) 的语法被调用,这种对象称为函数对象(又称仿函数)。重载时需在类内定义一个名为 operator() 的成员函数,其参数列表和返回类型可自由设计,并可包含多个重载版本以支持不同参数。函数对象相比普通函数指针的优势在于可以携带状态(通过成员变量保存数据),并且编译器容易将其内联从而提升性能,因此广泛应用于 C++ 标准库算法(如 std::sort 的比较器、std::for_each 的遍历操作)以及需要自定义行为的回调场景中,同时 Lambda 表达式在底层也正是通过编译器生成一个重载了 operator() 的匿名函数对象来实现的。

使用案例

案例一:函数对象的使用

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

// 函数对象(仿函数)
class BiggerThanZero {
public:
// 函数运算符 operator() 重载
bool operator()(const int val) const {
return val > 0;
}
};

int main() {
const BiggerThanZero btz;
const int result = btz(3);
std::cout << result << std::endl;
return 0;
}

程序运行的结果如下:

1
1

案例二:可被转换为函数指针的类对象(函数对象 / 仿函数)

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
#include <iostream>

// 可调用对象
class MyClass {
public:
// 定义一个函数指针类型
// fpoint 是一个指向「返回 void、接受一个 int 参数」的函数的指针类型
using fpoint = void (*)(int);

// 静态成员函数
// 注意:静态成员函数没有 this 指针,因此它的地址可以赋值给普通函数指针
static void process(int val) {
std::cout << "void process(int val)" << std::endl;
}

// 类型转换运算符(转换函数)
// 作用:把 MyClass 对象隐式或显式地转换成 fpoint(函数指针)类型
// 返回值为静态成员函数 process,从而实现「让对象像函数一样被调用」
operator fpoint() {
return process;
}
};

int main() {
// 创建 MyClass 对象
MyClass mc;

// mc(5) 并不是真正的「函数调用运算符重载」
// 由于 MyClass 定义了 operator fpoint(),编译器会先把 mc 隐式转换为函数指针 fpoint,然后再通过该函数指针调用 process(5)
// 等价于:((fpoint)mc)(5); 也等价于:MyClass::process(5);
mc(5);
}

程序运行的结果如下:

1
void process(int val)

Lambda 表达式

核心概念

Lambda 表达式是 C++ 11 引入的一种匿名函数对象语法。它允许在调用现场就地定义一个可调用体,无需事先在别处声明具名函数或函数对象类,从而把 “一小段行为” 作为参数直接传递给算法、回调注册接口或异步任务。从本质上看,编译器会为每个 Lambda 表达式生成一个独一无二的闭包类型,并构造出该类型的临时对象(闭包)。因此 Lambda 表达式并不是某种运行时魔法,而是一种语法糖,最终仍归结为普通的函数对象。值得一提的是,在 C++ 11 中,Lambda 表达式的类型被称为 “闭包类型(Closure Type)”,而由该表达式在运行时创建的具体对象即为 “闭包”(本质是函数内的可调用对象)。

  • 基本构成

    • 一个 Lambda 表达式由若干可选的组成部分按固定顺序排列:
      • 捕获列表:方括号部分,声明 Lambda 表达式体内可以使用哪些外部变量,以及以值还是以引用的方式使用。
      • 参数列表:圆括号部分,与普通函数的形参含义一致,C++ 14 起支持 auto 泛型参数。
      • 说明符与异常规格:如 mutable、constexpr、noexcept 等修饰。
      • 返回类型:可用尾置返回类型显式指定;省略时由编译器根据函数体推导。
      • 函数体:花括号内的实际执行逻辑。
    • 除捕获列表外,其余部分在满足条件时均可省略,这也解释了为什么最简单的 Lambda 表达式可以短到只有一对空方括号加一对花括号。
  • 捕获机制

    • 捕获是 Lambda 表达式区别于普通函数的核心能力,它决定了闭包如何 “记住” 外部状态:
      • 按值捕获:在闭包对象中保存外部变量的副本,生命周期与闭包一致,外部后续修改不影响副本。默认情况下函数体对这些副本是只读的。
      • 按引用捕获:闭包仅保存引用,读写的是原变量,因此必须警惕悬垂引用 —— 尤其是闭包生存期超过被引用对象作用域的情况。
      • 隐式捕获:用默认捕获符统一指定按值或按引用,再对个别变量做例外处理。实践中建议尽量显式列出捕获项,以提升可读性并规避意外的悬垂问题。
      • 初始化捕获:C++ 14 起支持在捕获列表中直接定义新变量(即广义捕获),可以把外部值移动进闭包,或为捕获的变量起别名,这使得 Lambda 表达式能够持有只能移动的类型。
    • Lambda 表达式无法捕获静态局部变量,但可以在函数体内直接使用。
    • Lambda 表达式中,默认的几种捕获方式:
      • [&] 表示按引用捕获外部作用域中的所有变量。
      • [=] 表示按值捕获外部作用域中的所有变量,并在函数体内以副本形式使用,默认不能修改这些副本(即只读)。
      • [this] 表示捕获当前对象的 this 指针;通过该指针可直接访问和修改当前对象的成员。它捕获的是 this 指针本身,而非 *this 对象的副本。
      • [*this] 表示按值捕获当前整个对象,是 C++ 17 引入的语法,捕获的是当前对象的副本(即捕获 *this 对象的副本),而不是捕获 this 指针,函数体内操作的是副本。
    • Lambda 表达式中,使用 mutable 关键字可以解除按值捕获副本的只读限制,使函数体可以修改这些副本 —— 注意修改的只是闭包内部的副本,而非外部原变量。
  • 类型与对象特性

    • 每个 Lambda 表达式的类型都是编译器生成的唯一闭包类型,无法在源码中直接书写其类型名,通常借助 auto 或模板参数接收。
    • 闭包类型具有以下特点:
      • 默认构造函数被删除(C++ 20 前无默认构造),拷贝构造与移动构造视捕获成员而定。
      • 若捕获列表为空,闭包可隐式转换为普通函数指针,便于传给 C 语言风格接口。
      • 函数调用运算符默认是 const 的,这正是按值捕获副本只读的原因。
  • 演进历史脉络

    • Lambda 表达式的能力随 C++ 标准迭代持续增强。
    • C++ 11:引入基本语法、值 / 引用捕获与隐式捕获。
    • C++ 14:加入泛型 Lambda 表达式(auto 参数)与初始化捕获。
    • C++ 17:支持 constexpr Lambda 表达式,并允许在捕获列表中用 *this 按值捕获整个对象。
    • C++ 20:支持模板形参列表、无捕获 Lambda 表达式的默认构造与赋值,以及更灵活的参数传递方式。
    • C++ 23 及以后:进一步放宽约束,如允许省略参数列表等简化写法。
  • 典型应用场景

    • Lambda 的价值在于把 “行为” 变成可传递的一等参数,常见用途包括:
      • 作为 C++ 标准算法库的谓词与比较器,替代繁琐的函数对象。
      • 注册回调、事件处理器与信号槽。
      • 自定义智能指针的删除器,或容器 / 哈希表的比较与哈希策略。
      • 编写异步任务、线程入口与延迟执行的闭包。
      • 在局部作用域中封装一次性逻辑,减少命名污染。
  • 使用建议

    • 优先显式捕获:明确列出所需变量,避免默认捕获带来的隐式依赖与悬垂风险。
    • 警惕引用捕获的生存期:闭包若被存储或异步执行,引用捕获极易失效;此时应改用按值或初始化捕获转移所有权。
    • 控制体量:Lambda 表达式适合短小逻辑,过长或复用频繁时应提炼为具名函数或函数对象。
    • 注意 mutable 的语义:它改变的是闭包内部状态,不要误以为能修改外部变量。
    • 理解零开销:Lambda 表达式与手写函数对象在性能上等价,可放心用于性能敏感的业务场景。

总结

Lambda 表达式把函数式编程的便利带入了 C++,以极低的语法成本实现了就地定义、按需捕获的可调用对象。它既保持了与手写函数对象一致的零开销抽象特性,又显著提升了回调与算法定制场景下的代码可读性。掌握捕获语义与生存期规则,是正确、安全使用 Lambda 的关键。值得注意的是,Lambda 表达式使用按值捕获时,捕获的副本在函数体内默认是 const 的(因为 Lambda 表达式的 operator() 默认是 const),即默认不可以修改副本,只有添加 mutable 关键字后才能修改副本,但仍不影响外部原变量的值。

使用案例

下述案例覆盖了 Lambda 表达式的各种使用方式:捕获方式的选择、作为标准库算法的谓词、初始化捕获转移所有权、泛型参数、回调注册与异步任务、自定义删除器 / 比较器、IIFE 以及存储方式等。在实际编码中,按值捕获用于跨作用域存活、按引用捕获用于即用即弃,是 Lambda 表达式避免生命周期问题的核心原则。

使用案例一
  • Lambda 表达式的基础用法与捕获方式
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>

// 用于演示 [this] 捕获:Lambda 在成员函数内访问当前对象
class Widget {
public:
void demoThis() {
// [this] 按值捕获 this 指针,可访问成员 value
auto printValue = [this]() {
std::cout << "[this] value = " << value << "\n"; // 输出 [this] value = 100
};
printValue();

// [this] 配合修改成员 value
auto bump = [this]() { value += 1; };
bump();
std::cout << "[this] after bump = " << value << "\n"; // 输出 [this] after bump = 101
}

private:
int value = 100;
};

int main() {
int a = 10;
int b = 20;
int c = 1;
int d = 2;
int e = 3;
static int f = 4;

// 1. 无捕获:可隐式转换为函数指针
auto add = [](int a, int b) { return a + b; };
std::cout << add(1, 2) << "\n"; // 输出 3

// 2. 按值捕获:闭包内保存 a 的副本,外部后续修改不影响副本
auto byValue = [a](int x) { return x + a; };
a = 100; // 修改外部变量的值
std::cout << byValue(5) << "\n"; // 输出 15,仍是旧值

// 3. mutable:允许修改按值捕获的副本(不影响外部变量的值)
auto counter = [b]() mutable { return ++b; };
counter();
std::cout << b << "\n"; // 输出 20,仍是旧值

// 4. 按引用捕获:读写的是原变量
auto byRef = [&a](int x) { a += x; };
byRef(7);
std::cout << a << "\n"; // 输出 107,是新值

// 5. [&] 隐式按引用捕获:Lambda 内用到的外部变量全部按引用捕获
auto refAll = [&]() {
c += 10; // 直接修改外部变量
d += 20;
std::cout << "[&] c=" << c << " d=" << d << "\n"; // 输出 [&] c=11 d=22
};
refAll();
std::cout << "[&] outside c=" << c << " d=" << d << "\n"; // 输出 [&] outside c=11 d=22

// 6. [=] 隐式按值捕获:Lambda 内用到的外部变量全部按值捕获
auto valAll = [=]() {
// c、d、e 均为副本;默认只读,不能修改
std::cout << "[=] c=" << c << " d=" << d << " e=" << e << "\n"; // 输出 [=] c=11 d=22 e=3
};
c = 999; // 修改外部变量的值
valAll(); // 仍打印副本值,不受外部影响
std::cout << "[=] outside c=" << c << "\n"; // 输出 [=] outside c=999

// 7. 隐式捕获 + 例外:默认按值捕获,e 改为按引用捕获
auto mixed = [=, &e]() {
e += 100; // e 是引用,可修改外部变量的值
std::cout << "[=,&e] c=" << c << " e=" << e << "\n"; // 输出 [=,&e] c=999 e=103
};
mixed();
std::cout << "[=,&e] outside e=" << e << "\n"; // 输出 [=,&e] outside e=103

// 8. [this] 捕获:在成员函数内通过 Lambda 访问当前对象成员
Widget w;
w.demoThis();

// 9. 静态局部变量无需捕获即可使用
auto process = []() { std::cout << "[] f = " << f << std::endl; }; // 输出 [] f = 4
process();

return 0;
}
使用案例二
  • Lambda 表达式作为 C++ 标准库算法的谓词
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
std::vector<int> v{5, 2, 8, 1, 9, 3};

// 排序:自定义比较器(降序,从大到小排序)
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// 查找第一个满足条件的元素
auto it = std::find_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
if (it != v.end()) std::cout << "第一个偶数: " << *it << "\n"; // 输出 8

// 计数满足条件的元素
int oddCount = std::count_if(v.begin(), v.end(), [](int x) { return x % 2 != 0; });
std::cout << "奇数个数: " << oddCount << "\n"; // 输出 4

// for_each 遍历并打印
std::for_each(v.begin(), v.end(), [](int x) { std::cout << x << " "; });
std::cout << "\n"; // 输出 9 8 5 3 2 1

return 0;
}
使用案例三
  • 初始化捕获(从 C++ 14 开始支持):转移所有权
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>
#include <utility>

int main() {
auto ptr = std::make_unique<int>(42);

// 将 unique_ptr 移动进闭包(unique_ptr 不可拷贝,只能移动捕获)
auto task = [p = std::move(ptr)]() { std::cout << "捕获的值: " << *p << "\n"; };

// ptr 此时已为空,所有权在闭包内
task(); // 输出 42

return 0;
}
使用案例四
  • Lambda 表达式使用 auto 参数(从 C++ 14 开始支持):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>

int main() {
// 参数类型由调用时推导,等价于模板化 operator()
auto print = [](const auto& value) { std::cout << value << "\n"; };

print(42); // 参数类型推导为 int,输出 42
print(std::string("hello")); // 参数类型推导为 string,输出 hello
print(3.14); // 参数类型推导为 double,输出 3.14

// 配合可变参数模板风格:接受多个参数
auto sum = [](auto... args) { return (args + ...); }; // C++ 17 折叠表达式
std::cout << sum(1, 2, 3, 4) << "\n"; // 输出 10

return 0;
}
使用案例五
  • 回调注册与异步任务
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
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

// 模拟一个接受回调的接口
void registerCallback(std::function<void(int)> cb) {
// 立即调用,模拟事件触发
cb(100);
}

int main() {
// 注册 Lambda 表达式作为回调
registerCallback([](int code) { std::cout << "收到事件码: " << code << "\n"; });

// 线程入口使用 Lambda 表达式,按值捕获避免悬垂引用
int id = 7;
std::thread t([id]() { std::cout << "线程 " << id << " 运行中\n"; });
t.join();

// 存储多个 Lambda 表达式到容器中统一调用
std::vector<std::function<int(int)>> ops;
ops.push_back([](int x) { return x + 1; });
ops.push_back([](int x) { return x * 2; });
for (auto& op : ops) {
std::cout << op(10) << " ";
} // 输出 11 20

return 0;
}
使用案例六
  • 自定义 STL 容器的删除器与比较器
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
#include <iostream>
#include <memory>
#include <set>

int main() {
// 自定义删除器:Lambda 表达式作为 deleter,替代手写函数对象
auto deleter = [](int* ptr) {
std::cout << "释放资源 " << *ptr << "\n"; // 输出:释放资源 88
delete ptr;
};

// 离开作用域时,unique_ptr 会自动调用 deleter 释放内存
std::unique_ptr<int, decltype(deleter)> up(new int(88), deleter);

// 自定义比较器:Lambda 定义 set 容器的排序规则(降序,从大到小排序)
auto cmp = [](int a, int b) { return a > b; };
std::set<int, decltype(cmp)> s(cmp);
s.insert({3, 4, 1, 5});
for (int x : s) {
std::cout << x << " ";
} // 输出 5 4 3 1
std::cout << "\n";

return 0;
}
使用案例七
  • IIFE:立即调用的 Lambda 表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

int main() {
// 立即调用:用于初始化复杂常量,避免污染外层作用域
const int result = [] {
int sum = 0;
for (int i = 1; i <= 10; ++i) {
sum += i;
}
return sum;
}(); // 注意末尾的 () 用于触发调用

std::cout << "1 ... 10 之和: " << result << "\n"; // 输出 55
return 0;
}
使用案例八
  • Lambda 表达式的存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <functional>
#include <iostream>

int main() {
// 使用 std::function 存储 Lambda 表达式
int number = 5;
std::function<int(int, int)> fun = [number](const int x, const int y) -> int { return x + y + number; };
const int result = fun(3, 5);
std::cout << result << std::endl; // 输出 13

// 使用函数指针存储不捕获任何变量的 Lambda 表达式
using funType = int (*)(const int, const int);
funType fun2 = [](const int x, const int y) -> int { return x + y; };
const int result2 = fun2(8, 10);
std::cout << result2 << std::endl; // 输出 18
}
使用陷阱
使用陷阱一
  • Lambda 表达式的悬垂引用陷阱
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>

std::function<int()> makeBadClosure() {
int local = 42;

// 危险写法:按引用捕获局部变量,函数返回后 local 已销毁
return [&local]() { return local; };
}

std::function<int()> makeGoodClosure() {
int local = 42;

// 正确写法:按值捕获,副本随闭包存活
return [local]() { return local; };
}

int main() {
auto bad = makeBadClosure();
// std::cout << bad(); // 发生未定义行为,存在悬垂引用问题

auto good = makeGoodClosure();
std::cout << good() << "\n"; // 输出 42,不会发生未定义行为

return 0;
}

代码分析

上述案例代码的悬垂引用问题出在 makeBadClosure 中:Lambda 用 [&local] 按引用捕获了函数 makeBadClosure 的局部变量 local,而这个 Lambda 被包装成 std::function 返回给调用者后,makeBadClosure 的栈帧已经销毁,局部变量 local 的生命周期随之结束,但返回的闭包内部仍保存着指向该已销毁变量的引用;因此在 main 中调用 bad() 时,Lambda 函数体访问 local 实际上是在读取一块已经失效的栈内存,属于典型的悬垂引用和未定义行为 —— 这也是为什么注释掉的那行 std::cout << bad() 可能 "偶尔正常" 却绝不可靠的原因。相比之下,makeGoodClosure 用 [local] 按值捕获,闭包内部持有的是 local 的副本,副本随闭包对象一起存活,函数返回后依然有效,因此 good() 能稳定输出 42。

使用陷阱二
  • Lambda 表达式捕获成员变量的陷阱(悬垂 this 指针)
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
#include <functional>
#include <iostream>
#include <vector>

std::vector<std::function<bool(int)>> vec;

class MyClass {
public:
void addItem() {
// 错误写法:按值捕获成员变量,相当于捕获了 this 指针([this]),存在悬垂 this 指针导致的未定义行为
vec.push_back([=](const int val) { return val % m_num == 0; });

// 正确写法:拷贝成员变量到临时变量,Lambda 表达式按值捕获临时变量,然后在函数体内使用临时变量的副本
// int tmp = m_num;
// vec.push_back([tmp](const int val) { return val % tmp == 0; });
}

private:
int m_num = 5;
};

int main() {
MyClass* mc = new MyClass();
mc->addItem();
delete mc;

int result = vec[0](10);
std::cout << result << std::endl;

return 0;
}

代码分析

在上述案例代码中,Lambda 写成 [=] 时,在成员函数里捕获的并不是成员变量 m_num 本身,而是隐式捕获了 this 指针(相当于 [this],因为 m_num 等价于 this->m_num),Lambda 函数体内访问 m_num 实际是通过这个保存下来的 this 指针去解引用。addItem() 里把闭包存入全局 vec 后,main 中执行 delete mc 销毁了 MyClass 对象,此时 Lambda 内部的 this 就变成了悬垂指针;随后 vec[0](10) 调用时,Lambda 函数体通过这个已失效的 this 访问 m_num,是在读写已释放对象的内存,属于典型的未定义行为 —— 即便 [=] 看起来像是 "按值捕获",也没有将 m_num 的值复制进闭包。正确做法之一是在 C++ 14 及以后用初始化捕获显式复制成员变量:vec.push_back([num = m_num](int val) { return val % num == 0; });,这样闭包就会持有 m_num 的副本,不再依赖对象的生命周期。

使用陷阱三
  • Lambda 表达式捕获静态局部变量的陷阱
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
#include <functional>
#include <iostream>
#include <vector>

std::vector<std::function<bool(int)>> vec;

void process() {
// 静态局部变量
static int num = 3;

// Lambda 表达式无法捕获静态局部变量,但可以直接使用,效果相当于按引用捕获,而不是按值捕获
vec.push_back([](const int val) {
std::cout << "num = " << num << std::endl; // 输出 4
return val % num == 0;
});

num++;
}

int main() {
process();
int result = vec[0](5);
std::cout << result << std::endl;
return 0;
}

代码分析

上述案例代码可以正常输出 num = 4 和 0,但它的关键点在于:Lambda 捕获列表是空的 [],却仍然能访问 process() 内的静态局部变量 num —— 这正说明静态局部变量根本不需要被捕获。原因在于 static int num 虽然写在函数内部,但它的存储期是整个程序运行期,存放在静态存储区而非栈帧中,具有固定的地址和与程序同寿命的生命周期;Lambda 函数体内对 num 的访问在编译期就被解析为直接引用这个具有静态存储期的对象(类似访问全局变量),而不是像捕获普通变量那样在闭包对象里保存一份副本或一个引用成员。因此从效果上看,闭包对静态局部变量的访问等价于 "按引用访问" —— 它始终指向那个唯一且长期存在的 num 本体,process() 返回后 num 依然有效,之后 num++ 把它改成 4,vec[0](5) 调用时读到的就是更新后的 4(5 % 4 != 0,返回 0,即 false)。也正因如此,Lambda 表达式无法通过捕获列表对静态局部变量做 "按值捕获" 来冻结某个时刻的值,捕获列表对静态存储期变量不生效;若确实想在闭包里保存当时的值快照,必须显式引入一个普通变量副本再按值捕获它。

function 类模板

核心概念

std::function 是 C++ 11 引入于 <functional> 头文件中的一个通用多态函数包装器类模板,它能够以统一的方式存储、复制和调用任何满足指定签名的可调用对象。其模板参数采用 std::function<返回类型(参数类型...)> 的形式,例如 std::function<int(int, int)> 表示一个 “接受两个 int 并返回 int“ 的可调用对象包装器。与只能指向普通函数或静态成员函数的函数指针不同,std::function 可以容纳普通函数、函数指针、函数对象(重载了 operator() 的类实例)、Lambda 表达式、std::bind 的绑定结果以及成员函数指针(配合对象或绑定器)等几乎所有可调用实体,只要它们的调用签名兼容即可。它内部通过类型擦除(type erasure)机制实现,因此不同类型的可调用对象都能被同一 std::function 变量持有,并在运行时被统一调用。使用上,它可以像普通函数一样通过 operator() 调用,也可以为空(默认构造或赋值为 nullptr 时),调用空 std::function 会抛出 std::bad_function_call 异常,因此调用前通常用 if (f) 检查其是否有效。std::function 的代价是相比函数指针和函数对象有额外的类型擦除与可能的堆分配开销,性能略低且不利于内联,因此在性能敏感或需要极致轻量的场景下仍会优先考虑函数指针或模板;但在需要存储异构回调、实现事件系统、回调注册表或运行时动态选择行为的场合,std::function 提供了远比函数指针灵活且类型安全的解决方案。

特别注意

std::function 无法直接把一个重载函数名(重载集)赋给它并自动完成重载解析,因为其构造函数模板不会以目标函数签名作为推导依据,面对重载集时编译器无法确定该选哪个重载函数,导致推导失败、编译报错;解决方法是先用目标函数签名完成重载解析再交给 std::function,常见做法有三种 —— 用 static_cast<void(*)(int)>(f)(或 C 语言风格转换)显式指定函数签名、先用一个函数签名明确的函数指针变量接收重载函数再赋给 std::function、或用 Lambda 表达式包一层在函数体内调用重载函数让解析在体内发生,此外也可以通过 std::bind 绑定参数后消除重载歧义。简而言之,std::function 不能直接绑定重载函数集,因为它缺少重载解析所需的目标函数签名上下文,必须通过显式转换、函数指针中转或 Lambda 表达式包装先选定具体重载函数,再交由 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <functional>
#include <iostream>
#include <string>
#include <vector>

// 普通函数
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

int multiply(int a, int b) {
return a * b;
}

// 接受 std::function 作为参数的函数
int compute(int x, int y, std::function<int(int, int)> op) {
return op(x, y);
}

int main() {
// 1. 用 std::function 包装普通函数
std::function<int(int, int)> f = add;
std::cout << "f(3, 4) = " << f(3, 4) << std::endl;

// 2. 重新赋值为另一个普通函数
f = subtract;
std::cout << "f(10, 4) = " << f(10, 4) << std::endl;

// 3. 作为函数参数传递(回调)
std::cout << "compute(5, 6, add) = " << compute(5, 6, add) << std::endl;
std::cout << "compute(5, 6, multiply) = " << compute(5, 6, multiply) << std::endl;

// 4. 将普通函数存入容器,实现分派表
std::vector<std::function<int(int, int)>> ops = {add, subtract, multiply};
std::vector<std::string> names = {"add", "subtract", "multiply"};
for (size_t i = 0; i < ops.size(); ++i) {
std::cout << names[i] << "(8, 2) = " << ops[i](8, 2) << std::endl;
}

// 5. 空 std::function 检查
std::function<int(int, int)> g;
if (!g) {
std::cout << "g is empty" << std::endl;
}
g = add;
if (g) {
std::cout << "g(1, 2) = " << g(1, 2) << std::endl;
}

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
f(3, 4) = 7
f(10, 4) = 6
compute(5, 6, add) = 11
compute(5, 6, multiply) = 30
add(8, 2) = 10
subtract(8, 2) = 6
multiply(8, 2) = 16
g is empty
g(1, 2) = 3
结合函数指针使用
  • 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <functional>
#include <iostream>

// 普通函数
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

// 接受函数指针作为参数的函数
int applyPtr(int x, int y, int (*op)(int, int)) {
return op(x, y);
}

// 接受 std::function 作为参数的函数
int applyFunc(int x, int y, std::function<int(int, int)> op) {
return op(x, y);
}

int main() {
// 1. 定义函数指针并指向普通函数
int (*fp)(int, int) = add;
std::cout << "fp(3, 4) = " << fp(3, 4) << std::endl;

// 2. 用 std::function 包装该函数指针
std::function<int(int, int)> f = fp;
std::cout << "f(3, 4) = " << f(3, 4) << std::endl;

// 3. 让函数指针改指向另一函数,再赋给 std::function
fp = subtract;
f = fp;
std::cout << "f(10, 4) = " << f(10, 4) << std::endl;

// 4. 把函数指针直接传给接受 std::function 的函数
std::cout << "applyFunc(5, 6, add) = " << applyFunc(5, 6, add) << std::endl;

// 5. 把 std::function 取出为函数指针再传给接受函数指针的函数
int (*fp2)(int, int) = add;
std::function<int(int, int)> f2 = fp2;
std::cout << "applyPtr(7, 2, f2.target<int(*)(int,int)>() ? "
"*f2.target<int(*)(int,int)>() : nullptr) = ";
auto extracted = f2.target<int (*)(int, int)>();
if (extracted) {
std::cout << applyPtr(7, 2, *extracted) << std::endl;
}

// 6. 二者混用:先用函数指针选择,再用 std::function 统一保存
int (*ops[])(int, int) = {add, subtract};
std::function<int(int, int)> selected = ops[1];
std::cout << "selected(9, 3) = " << selected(9, 3) << std::endl;

// 7. 空 std::function 与函数指针的对比
std::function<int(int, int)> empty;
int (*nullFp)(int, int) = nullptr;
if (!empty && !nullFp) {
std::cout << "both empty and null" << std::endl;
}

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
fp(3, 4) = 7
f(3, 4) = 7
f(10, 4) = 6
applyFunc(5, 6, add) = 11
applyPtr(7, 2, f2.target<int(*)(int,int)>() ? *f2.target<int(*)(int,int)>() : nullptr) = 9
selected(9, 3) = 6
both empty and null
结合函数对象使用
  • 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <functional>
#include <iostream>
#include <vector>

// 函数对象:加法器,可携带状态
class Adder {
int base_;

public:
explicit Adder(int base) : base_(base) {
}
int operator()(int x) const {
return base_ + x;
}
};

// 函数对象:乘法器,可携带状态
class Multiplier {
int factor_;

public:
explicit Multiplier(int factor) : factor_(factor) {
}
int operator()(int x) const {
return factor_ * x;
}
};

// 函数对象:计数器,调用时递增并返回调用次数
class Counter {
int count_ = 0;

public:
int operator()() {
return ++count_;
}
};

// 接受 std::function 作为参数的函数
int apply(int x, std::function<int(int)> op) {
return op(x);
}

int main() {
// 1. 用 std::function 包装函数对象
Adder add5(5);
std::function<int(int)> f = add5;
std::cout << "f(10) = " << f(10) << std::endl;

// 2. 重新赋值为另一个函数对象
Multiplier mul3(3);
f = mul3;
std::cout << "f(10) = " << f(10) << std::endl;

// 3. 直接构造临时函数对象并赋给 std::function
f = Adder(100);
std::cout << "f(1) = " << f(1) << std::endl;

// 4. 将函数对象作为参数传递
std::cout << "apply(7, Adder(2)) = " << apply(7, Adder(2)) << std::endl;

// 5. 函数对象携带状态:同一个对象被多次调用
Counter counter;
std::function<int()> c = std::ref(counter);
std::cout << "c() = " << c() << std::endl;
std::cout << "c() = " << c() << std::endl;
std::cout << "c() = " << c() << std::endl;
std::cout << "counter() = " << counter() << std::endl;

// 6. 将不同函数对象存入同一容器
std::vector<std::function<int(int)>> ops;
ops.push_back(Adder(1));
ops.push_back(Multiplier(2));
ops.push_back(Adder(10));
for (size_t i = 0; i < ops.size(); ++i) {
std::cout << "ops[" << i << "](5) = " << ops[i](5) << std::endl;
}

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
f(10) = 15
f(10) = 30
f(1) = 101
apply(7, Adder(2)) = 9
c() = 1
c() = 2
c() = 3
counter() = 4
ops[0](5) = 6
ops[1](5) = 10
ops[2](5) = 15
结合 Lambda 使用
  • std::function 与 Lambda 表达式结合使用
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <functional>
#include <iostream>
#include <vector>

// 接受 std::function 作为参数的函数
int apply(int x, std::function<int(int)> op) {
return op(x);
}

int main() {
// 1. 用 std::function 包装无捕获 Lambda
std::function<int(int)> f = [](int x) { return x + 1; };
std::cout << "f(10) = " << f(10) << std::endl;

// 2. 重新赋值为另一个 Lambda
f = [](int x) { return x * x; };
std::cout << "f(10) = " << f(10) << std::endl;

// 3. 包装带捕获的 Lambda,携带状态
int base = 100;
f = [base](int x) { return base + x; };
std::cout << "f(5) = " << f(5) << std::endl;

// 4. 按引用捕获,共享外部变量
int counter = 0;
std::function<int()> tick = [&counter]() { return ++counter; };
std::cout << "tick() = " << tick() << std::endl;
std::cout << "tick() = " << tick() << std::endl;
std::cout << "counter = " << counter << std::endl;

// 5. 将 Lambda 作为参数传递
std::cout << "apply(7, [](int x){ return x - 3; }) = " << apply(7, [](int x) { return x - 3; }) << std::endl;

// 6. 将多个 Lambda 存入同一容器
std::vector<std::function<int(int)>> ops;
ops.push_back([](int x) { return x + 1; });
ops.push_back([](int x) { return x * 2; });
ops.push_back([](int x) { return x - 10; });
for (size_t i = 0; i < ops.size(); ++i) {
std::cout << "ops[" << i << "](6) = " << ops[i](6) << std::endl;
}

// 7. 返回 Lambda 的工厂函数
auto makeAdder = [](int n) -> std::function<int(int)> { return [n](int x) { return n + x; }; };
std::function<int(int)> add5 = makeAdder(5);
std::function<int(int)> add20 = makeAdder(20);
std::cout << "add5(1) = " << add5(1) << std::endl;
std::cout << "add20(1) = " << add20(1) << std::endl;

// 8. 空 std::function 检查
std::function<int(int)> empty;
if (!empty) {
std::cout << "empty is not callable" << std::endl;
}
empty = [](int x) { return x; };
if (empty) {
std::cout << "empty(42) = " << empty(42) << std::endl;
}

return 0;
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
f(10) = 11
f(10) = 100
f(5) = 105
tick() = 1
tick() = 2
counter = 2
apply(7, [](int x){ return x - 3; }) = 4
ops[0](6) = 7
ops[1](6) = 12
ops[2](6) = -4
add5(1) = 6
add20(1) = 21
empty is not callable
empty(42) = 42

std::bind 绑定器

核心概念

std::bind 是 C++ 11 引入的函数适配工具,用于将可调用对象(函数、函数指针、成员函数指针、函数对象等)与部分或全部实参绑定,生成一个新的可调用对象,从而实现参数预设、参数顺序重排、占位符延迟传参以及成员函数的对象绑定等效果。std::bind 的语法是 std::bind(可调用对象, 绑定参数...),绑定参数有两种用法:其一是用占位符(如 std::placeholders::_1)表示调用时才传入的实参,例如 auto f = std::bind(add, std::placeholders::_1, std::placeholders::_2); 调用 f(3, 5) 等价于 add(3, 5);其二是直接绑定具体值以预先固定部分参数,例如 auto g = std::bind(add, 10, std::placeholders::_1); 调用 g(5) 等价于 add(10, 5)。值得一提的是,std::bind 常与 std::function 配合使用,是 Lambda 表达式出现之前实现回调、事件处理和延迟调用的重要手段,但由于其语法冗长、占位符可读性差且在现代 C++ 中大多数场景已被更直观、通常也更高效的 Lambda 表达式所取代,如今更多出现在遗留代码或需要动态参数重排的特定场合。

特别注意

请留意 std::bind 的核心传参特性:对于事先绑定的参数,std::bind 默认采用值传递进行拷贝(例如 int a = 2; auto bf = std::bind(myfunc, a, std::placeholders::_1);,此时 a 的值被拷贝进 bf),后续原变量改变不影响绑定对象;而对于未事先绑定、使用占位符的参数,在实际调用时通过完美转发传递(例如 int b = 3; bf(b); 中的 b,占位符 std::placeholders::_1 会将其原样转发,若传入左值则表现为引用语义),从而保持对原始实参的引用。若想让预先绑定的参数也按引用传递,必须显式使用 std::ref 或 std::cref 包裹(如 std::bind(myfunc, std::ref(a), std::placeholders::_1))。

使用案例
使用案例一
  • 预设部分参数(参数绑定)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 将二元函数的一个参数固定,得到一元可调用对象:
*/

#include <functional>
#include <iostream>

// 一个普通的二元函数,作为被绑定的目标
int add(int a, int b) {
return a + b;
}

int main() {
// std::bind 把 add 的第一个参数固定为 10
// std::placeholders::_1 表示"新函数调用时的第 1 个实参"会填到这里(即 b 的位置)
// 于是 add10 变成一个只需 1 个参数的一元函数
auto add10 = std::bind(add, 10, std::placeholders::_1);

std::cout << add10(5) << std::endl; // 输出 15,等价于 add(10, 5)
std::cout << add10(20) << std::endl; // 输出 30,等价于 add(10, 20)
}
  • 等效的 Lambda 表达式写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// 被调用的二元函数
int add(int a, int b) {
return a + b;
}

int main() {
// lambda 直接捕获常量 10,等价于 std::bind(add, 10, _1)
auto add10 = [](int x) { return add(10, x); };

std::cout << add10(5) << std::endl; // 输出 15
std::cout << add10(20) << std::endl; // 输出 30
}
使用案例二
  • 调整参数顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 用占位符重排实参位置,这是 Lambda 表达式较难直观表达的用法
*/
#include <functional>
#include <iostream>

// 按 a、b 的顺序打印两个参数
void print(int a, int b) {
std::cout << "a=" << a << ", b=" << b << std::endl;
}

int main() {
// 关键点:占位符的顺序决定了新函数的实参如何映射到原函数
// _2 放在第一个位置 -> 新函数的第 2 个实参传给 a
// _1 放在第二个位置 -> 新函数的第 1 个实参传给 b
auto swapped = std::bind(print, std::placeholders::_2, std::placeholders::_1);

swapped(1, 2); // 输出 a=2, b=1(实参顺序被交换了)
}
  • 等效的 Lambda 表达式写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// 按 a、b 顺序打印
void print(int a, int b) {
std::cout << "a=" << a << ", b=" << b << std::endl;
}

int main() {
// lambda 内部把参数顺序对调后调用 print
// 等价于 std::bind(print, _2, _1)
auto swapped = [](int a, int b) { print(b, a); };

swapped(1, 2); // 输出 a=2, b=1
}
使用案例三
  • 绑定成员函数与对象
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
/**
* 将成员函数和具体对象绑定,生成普通可调用对象
*/

#include <functional>
#include <iostream>

class Calculator {
public:
// 构造函数
Calculator(const int i) : base(i) {
}

// 成员函数,隐含 this 参数
int add(const int x) const {
return base + x;
}

private:
// 成员变量,作为加法的基数
int base;
};

int main() {
Calculator calc(100); // 创建一个对象,base = 100

// 绑定成员函数指针 &Calculator::add 与具体对象 calc
// 相当于把隐含的 this 固定为 &calc,剩下的参数用 _1 占位
auto f = std::bind(&Calculator::add, calc, std::placeholders::_1);

std::cout << f(5) << std::endl; // 输出 105,等价于 calc.add(5)
}
  • 等效的 Lambda 表达式写法
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 <iostream>

class Calculator {
public:
// 构造函数
Calculator(const int i) : base(i) {
}

// 成员函数,隐含 this 参数
int add(const int x) const {
return base + x;
}

private:
// 成员变量,作为加法的基数
int base;
};

int main() {
Calculator calc(100); // 创建一个对象,base = 100

// 按值捕获对象 calc,等价于 std::bind(&Calculator::add, calc, _1)
auto f = [calc](int x) { return calc.add(x); };

std::cout << f(5) << std::endl; // 输出 105,等价于 calc.add(5)
}
使用案例四
  • 绑定成员函数配合对象指针
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
/**
* 在容器遍历、回调注册中常用,用 _1 表示运行时传入的对象:
*/

#include <functional>
#include <iostream>
#include <vector>

class Item {
public:
// 构造函数
Item(const int i) : value(i) {
}

// 成员函数,无额外参数
void show() const {
std::cout << value << " ";
}

private:
int value;
};

int main() {
std::vector<Item> items{{1}, {2}, {3}};

// 只绑定成员函数,对象本身用 _1 延迟传入
// 这样 showItem 就是一个"接收 Item 并调用其 show()"的可调用对象
auto showItem = std::bind(&Item::show, std::placeholders::_1);

// 遍历时把每个元素作为实参传给 showItem
for (const auto& it : items) {
showItem(it);
} // 输出 1 2 3

std::cout << std::endl;
}
  • 等效的 Lambda 表达式写法
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
#include <iostream>
#include <vector>

class Item {
public:
// 构造函数
Item(const int i) : value(i) {
}

// 成员函数,无额外参数
void show() const {
std::cout << value << " ";
}

private:
int value;
};

int main() {
std::vector<Item> items{{1}, {2}, {3}};

// lambda 接收一个 Item 引用并调用其 show()
// 等价于 std::bind(&Item::show, _1)
auto showItem = [](const Item& it) { it.show(); };

for (const auto& it : items) {
showItem(it);
} // 输出 1 2 3

std::cout << std::endl;
}
使用案例五
  • 作为回调传给 C++ 标准库算法
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
/**
* 结合 std::function 存储可调用对象,实现延迟调用与回调
*/

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

// 普通函数
int add(const int a, const int b) {
return a + b;
}

int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6};

// std::greater<int>() 是一个二元函数对象(判断 a > b)
// 把它的第二个参数固定为 3,得到一元谓词:判断 x > 3
auto greaterThan3 = std::bind(std::greater<int>(), std::placeholders::_1, 3);

// find_if 会对每个元素调用谓词,返回第一个满足条件的迭代器
auto it = std::find_if(v.begin(), v.end(), greaterThan3);
if (it != v.end()) {
std::cout << *it << std::endl; // 输出 4
}

// std::bind 的结果可以存入 std::function,便于做回调注册 / 类型擦除
std::function<int(int)> add5 = std::bind(add, 5, std::placeholders::_1);
std::cout << add5(10) << std::endl; // 输出 15,等价于 add(5, 10)
}
  • 等效的 Lambda 表达式写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6};

// lambda 直接表达"x > 3"的一元谓词
// 等价于 std::bind(std::greater<int>(), _1, 3)
auto greaterThan3 = [](int x) { return x > 3; };

// find_if 返回第一个满足条件的迭代器
auto it = std::find_if(v.begin(), v.end(), greaterThan3);
if (it != v.end()) {
std::cout << *it << std::endl; // 输出 4
}

// std::function + lambda 的可调用对象存储
std::function<int(int)> add5 = [](int x) { return x + 5; };
std::cout << add5(10) << std::endl; // 输出 15
}