std::bind 和 std::function
程序员可以使用这一对工具来创建函数并将其绑定到变量。
std::bind 和 std::function 非常契合。std::bind 可以让我们即时创建新的函数对象,而 std::function 则将这些临时的函数对象绑定到变量上。两者都是来自函数式编程的强大工具,需要包含头文件 <functional>。
来看下面的例子:
1#include <algorithm> 2#include <functional> 3#include <iostream> 4#include <numeric> 5#include <vector> 6 7int main(){ 8 std::cout << std::endl; 9 10 std::vector<int> myVec(20); 11 std::iota(myVec.begin(), myVec.end(), 0); 12 13 std::cout << "myVec: "; 14 for (auto i: myVec) std::cout << i << " "; 15 std::cout << std::endl; 16 17 std::function<bool(int)> myBindPred = std::bind(std::logical_and<bool>(), 18 std::bind(std::greater<int>(), std::placeholders::_1, 9), 19 std::bind(std::less<int>(), std::placeholders::_1, 16)); 20 21 myVec.erase(std::remove_if(myVec.begin(), myVec.end(), myBindPred), myVec.end()); 22 23 std::cout << "myVec: "; 24 for (auto i: myVec) std::cout << i << " "; 25 std::cout << std::endl; 26}
🔑
std::bind和std::function大多是多余的
std::bind和std::function是 C++ TR1 的一部分,在 C++11 中已基本不再必要。现在我们更推荐使用 lambda 表达式替代std::bind,并且大多数情况下可以使用自动类型推导(auto)代替std::function。
接下来,我们详细讨论 std::bind 和 std::function 的行为。
std::bind
借助 std::bind,我们可以以多种方式创建函数对象:
- 将参数绑定到任意位置。
- 调整参数的顺序。
- 引入占位符来表示参数。
- 对函数进行部分求值。
- 调用新创建的函数对象,将其用于 STL 算法,或存储在
std::function中。
std::function
std::function 可以将任意可调用对象存储在变量中。它是一种多态函数包装器。可调用对象可以是 lambda 表达式、函数对象或普通函数。当必须显式指定可调用对象的类型时,std::function 是必需的,无法被 auto 替代。
为了更清楚地理解这一点,看下面的例子:
1#include <algorithm> 2#include <functional> 3#include <iostream> 4#include <iterator> 5#include <vector> 6 7double divMe(double a, double b){ 8 return double(a / b); 9} 10 11using namespace std::placeholders; 12 13int main(){ 14 std::cout << std::endl; 15 16 // 直接调用函数对象 17 std::cout << "1/2.0= " << std::bind(divMe, 1, 2.0)() << std::endl; 18 19 // 两个参数都使用占位符 20 std::function<double(double, double)> myDivBindPlaceholder = std::bind(divMe, _1, _2); 21 std::cout << "1/2.0= " << myDivBindPlaceholder(1, 2.0) << std::endl; 22 23 // 两个参数都使用占位符,但交换顺序 24 std::function<double(double, double)> myDivBindPlaceholderSwap = std::bind(divMe, _2, _1); 25 std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0, 1) << std::endl; 26 27 // 第一个参数使用占位符 28 std::function<double(double)> myDivBind1St = std::bind(divMe, _1, 2.0); 29 std::cout << "1/2.0= " << myDivBind1St(1) << std::endl; 30 31 // 第二个参数使用占位符 32 std::function<double(double)> myDivBind2Nd = std::bind(divMe, 1.0, _1); 33 std::cout << "1/2.0= " << myDivBind2Nd(2.0) << std::endl; 34 35 std::cout << std::endl; 36}
再看另一个例子:
1#include <cmath> 2#include <functional> 3#include <iostream> 4#include <map> 5 6int main(){ 7 std::cout << std::endl; 8 9 // 分发表 10 std::map<const char, std::function<double(double, double)>> dispTable; 11 dispTable.insert(std::make_pair('+', [](double a, double b){ return a + b; })); 12 dispTable.insert(std::make_pair('-', [](double a, double b){ return a - b; })); 13 dispTable.insert(std::make_pair('*', [](double a, double b){ return a * b; })); 14 dispTable.insert(std::make_pair('/', [](double a, double b){ return a / b; })); 15 16 // 执行计算 17 std::cout << "3.5+4.5= " << dispTable['+'](3.5, 4.5) << std::endl; 18 std::cout << "3.5-4.5= " << dispTable['-'](3.5, 4.5) << std::endl; 19 std::cout << "3.5*4.5= " << dispTable['*'](3.5, 4.5) << std::endl; 20 std::cout << "3.5/4.5= " << dispTable['/'](3.5, 4.5) << std::endl; 21 22 // 添加新操作 23 dispTable.insert(std::make_pair('^', [](double a, double b){ return std::pow(a, b); })); 24 std::cout << "3.5^4.5= " << dispTable['^'](3.5, 4.5) << std::endl; 25 26 std::cout << std::endl; 27};
这个机制是如何工作的?在这个例子中,分发表是一个 std::map,其键为 const char,值为 std::function<double(double, double)>。当然,也可以使用 std::unordered_map 代替 std::map。std::function 是一种多态函数包装器,能够接受任何表现得像函数的对象——无论是函数、函数对象还是 lambda 表达式(第12至15行)。std::function<double(double, double)> 的唯一要求是:其调用目标必须接受两个 double 类型参数,并返回一个 double 类型值。这些条件由 lambda 表达式满足。
我们在第18至21行使用了函数对象。因此,第25行中的 dispTable['^'] 返回的是由 lambda 表达式 [](double a, double b){ return std::pow(a, b);} 初始化的函数对象。要执行该函数对象,需要提供两个参数,这正是表达式 dispTable['^'](3.5, 4.5) 所做的。
std::map 是一种动态数据结构,因此我们可以在运行时添加并使用 '^' 操作(第25行)。
std::function 的类型参数定义了它所接受的可调用对象的类型。
| 函数类型 | 返回类型 | 参数类型 |
|---|---|---|
double(double, double) | double | double |
int() | int | |
double(int, double) | double | int, double |
void() |
返回类型与参数类型
更多信息
下一课将有一个练习,帮助我们更好地理解这一概念。