大纲 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 () { int (*p)(int , int ) = add; std::cout << "p(3, 4) = " << p (3 , 4 ) << std::endl; std::cout << "(*p)(3, 4) = " << (*p)(3 , 4 ) << std::endl; p = subtract; std::cout << "p(10, 4) = " << p (10 , 4 ) << std::endl; std::cout << "compute(5, 6, add) = " << compute (5 , 6 , add) << std::endl; std::cout << "compute(5, 6, multiply) = " << compute (5 , 6 , multiply) << std::endl; 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; } 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 ; }; int main () { MyClass mc (5 ) ; void (MyClass::*fpointer)(int ) = &MyClass::process; (mc.*fpointer)(10 ); return 0 ; }
程序运行的结果如下:
函数对象 核心概念 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 : 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 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 : using fpoint = void (*)(int ); static void process (int val) { std::cout << "void process(int val)" << std::endl; } operator fpoint () { return process; } }; int main () { MyClass mc; mc (5 ); }
程序运行的结果如下:
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 表达式避免生命周期问题的核心原则 。
使用案例一 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> class Widget {public : void demoThis () { auto printValue = [this ]() { std::cout << "[this] value = " << value << "\n" ; }; printValue (); auto bump = [this ]() { value += 1 ; }; bump (); std::cout << "[this] after bump = " << value << "\n" ; } 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 ; auto add = [](int a, int b) { return a + b; }; std::cout << add (1 , 2 ) << "\n" ; auto byValue = [a](int x) { return x + a; }; a = 100 ; std::cout << byValue (5 ) << "\n" ; auto counter = [b]() mutable { return ++b; }; counter (); std::cout << b << "\n" ; auto byRef = [&a](int x) { a += x; }; byRef (7 ); std::cout << a << "\n" ; auto refAll = [&]() { c += 10 ; d += 20 ; std::cout << "[&] c=" << c << " d=" << d << "\n" ; }; refAll (); std::cout << "[&] outside c=" << c << " d=" << d << "\n" ; auto valAll = [=]() { std::cout << "[=] c=" << c << " d=" << d << " e=" << e << "\n" ; }; c = 999 ; valAll (); std::cout << "[=] outside c=" << c << "\n" ; auto mixed = [=, &e]() { e += 100 ; std::cout << "[=,&e] c=" << c << " e=" << e << "\n" ; }; mixed (); std::cout << "[=,&e] outside e=" << e << "\n" ; Widget w; w.demoThis (); auto process = []() { std::cout << "[] f = " << f << std::endl; }; 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" ; int oddCount = std::count_if (v.begin (), v.end (), [](int x) { return x % 2 != 0 ; }); std::cout << "奇数个数: " << oddCount << "\n" ; std::for_each(v.begin (), v.end (), [](int x) { std::cout << x << " " ; }); std::cout << "\n" ; 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 ); auto task = [p = std::move (ptr)]() { std::cout << "捕获的值: " << *p << "\n" ; }; task (); 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 () { auto print = [](const auto & value) { std::cout << value << "\n" ; }; print (42 ); print (std::string ("hello" )); print (3.14 ); auto sum = [](auto ... args) { return (args + ...); }; std::cout << sum (1 , 2 , 3 , 4 ) << "\n" ; 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 () { registerCallback ([](int code) { std::cout << "收到事件码: " << code << "\n" ; }); int id = 7 ; std::thread t ([id]() { std::cout << "线程 " << id << " 运行中\n" ; }) ; t.join (); 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 ) << " " ; } 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 #include <iostream> #include <memory> #include <set> int main () { auto deleter = [](int * ptr) { std::cout << "释放资源 " << *ptr << "\n" ; delete ptr; }; std::unique_ptr<int , decltype (deleter) > up (new int (88 ), deleter) ; 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 << " " ; } std::cout << "\n" ; return 0 ; }
使用案例七 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" ; return 0 ; }
使用案例八 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <functional> #include <iostream> int main () { 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; 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; }
使用陷阱 使用陷阱一 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 ; return [&local]() { return local; }; } std::function<int () > makeGoodClosure () { int local = 42 ; return [local]() { return local; }; } int main () { auto bad = makeBadClosure (); auto good = makeGoodClosure (); std::cout << good () << "\n" ; 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 () { vec.push_back ([=](const int val) { return val % m_num == 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 的副本,不再依赖对象的生命周期。
使用陷阱三 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 ; vec.push_back ([](const int val) { std::cout << "num = " << num << std::endl; 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 保存和调用。
使用案例 结合普通函数使用 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; } int compute (int x, int y, std::function<int (int , int )> op) { return op (x, y); } int main () { std::function<int (int , int )> f = add; std::cout << "f(3, 4) = " << f (3 , 4 ) << std::endl; f = subtract; std::cout << "f(10, 4) = " << f (10 , 4 ) << std::endl; std::cout << "compute(5, 6, add) = " << compute (5 , 6 , add) << std::endl; std::cout << "compute(5, 6, multiply) = " << compute (5 , 6 , multiply) << std::endl; 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; } 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
结合函数指针使用 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); } int applyFunc (int x, int y, std::function<int (int , int )> op) { return op (x, y); } int main () { int (*fp)(int , int ) = add; std::cout << "fp(3, 4) = " << fp (3 , 4 ) << std::endl; std::function<int (int , int )> f = fp; std::cout << "f(3, 4) = " << f (3 , 4 ) << std::endl; fp = subtract; f = fp; std::cout << "f(10, 4) = " << f (10 , 4 ) << std::endl; std::cout << "applyFunc(5, 6, add) = " << applyFunc (5 , 6 , add) << std::endl; 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; } int (*ops[])(int , int ) = {add, subtract}; std::function<int (int , int )> selected = ops[1 ]; std::cout << "selected(9, 3) = " << selected (9 , 3 ) << std::endl; 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
结合函数对象使用 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_; } }; int apply (int x, std::function<int (int )> op) { return op (x); } int main () { Adder add5 (5 ) ; std::function<int (int )> f = add5; std::cout << "f(10) = " << f (10 ) << std::endl; Multiplier mul3 (3 ) ; f = mul3; std::cout << "f(10) = " << f (10 ) << std::endl; f = Adder (100 ); std::cout << "f(1) = " << f (1 ) << std::endl; std::cout << "apply(7, Adder(2)) = " << apply (7 , Adder (2 )) << std::endl; 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; 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> int apply (int x, std::function<int (int )> op) { return op (x); } int main () { std::function<int (int )> f = [](int x) { return x + 1 ; }; std::cout << "f(10) = " << f (10 ) << std::endl; f = [](int x) { return x * x; }; std::cout << "f(10) = " << f (10 ) << std::endl; int base = 100 ; f = [base](int x) { return base + x; }; std::cout << "f(5) = " << f (5 ) << std::endl; 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; std::cout << "apply(7, [](int x){ return x - 3; }) = " << apply (7 , [](int x) { return x - 3 ; }) << std::endl; 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; } 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; 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 () { auto add10 = std::bind (add, 10 , std::placeholders::_1); std::cout << add10 (5 ) << std::endl; std::cout << add10 (20 ) << std::endl; }
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 () { auto add10 = [](int x) { return add (10 , x); }; std::cout << add10 (5 ) << std::endl; std::cout << add10 (20 ) << std::endl; }
使用案例二 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <functional> #include <iostream> void print (int a, int b) { std::cout << "a=" << a << ", b=" << b << std::endl; } int main () { auto swapped = std::bind (print, std::placeholders::_2, std::placeholders::_1); swapped (1 , 2 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> void print (int a, int b) { std::cout << "a=" << a << ", b=" << b << std::endl; } int main () { auto swapped = [](int a, int b) { print (b, a); }; swapped (1 , 2 ); }
使用案例三 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) { } int add (const int x) const { return base + x; } private : int base; }; int main () { Calculator calc (100 ) ; auto f = std::bind (&Calculator::add, calc, std::placeholders::_1); std::cout << f (5 ) << std::endl; }
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) { } int add (const int x) const { return base + x; } private : int base; }; int main () { Calculator calc (100 ) ; auto f = [calc](int x) { return calc.add (x); }; std::cout << f (5 ) << std::endl; }
使用案例四 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 #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 }}; auto showItem = std::bind (&Item::show, std::placeholders::_1); for (const auto & it : items) { showItem (it); } std::cout << std::endl; }
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 }}; auto showItem = [](const Item& it) { it.show (); }; for (const auto & it : items) { showItem (it); } std::cout << std::endl; }
使用案例五 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 <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 }; auto greaterThan3 = std::bind (std::greater<int >(), std::placeholders::_1, 3 ); auto it = std::find_if (v.begin (), v.end (), greaterThan3); if (it != v.end ()) { std::cout << *it << std::endl; } std::function<int (int )> add5 = std::bind (add, 5 , std::placeholders::_1); std::cout << add5 (10 ) << std::endl; }
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 }; auto greaterThan3 = [](int x) { return x > 3 ; }; auto it = std::find_if (v.begin (), v.end (), greaterThan3); if (it != v.end ()) { std::cout << *it << std::endl; } std::function<int (int )> add5 = [](int x) { return x + 5 ; }; std::cout << add5 (10 ) << std::endl; }