内容概要
- 可调用对象概念
- 可调用对象的类型
- C++11 std::bind函数
- C++11 function类型
概念
Object that can appear as the left-hand operand of the call operator. Pointers to functions, lambdas, and objects of a class that defines an overloaded function call operator are all callable objects.
—-《C++ Primer 5th》
书中给出的是描述性定义。
“可调用”一词最容易让人联想到的是“函数”,在我的理解中,函数是抽象出来的有意义的代码块,C++提供了多种封装代码块的能力,这些能力生成的各种对象就是可调用对象。
类型
C++ has several kinds of callable objects: functions and pointers to functions, lambdas, objects created by bind , and classes that overload the function-call operator.
—-《C++ Primer 5th》
简而言之:函数(指针),lambda函数,std::bind生成的函数,重载了()的类的实体对象,bind函数后面再讨论
在STL算法中,不少算法可以让调用者选择自己的断言函数,那个断言函数就是一个符合某种特定要求的可调用对象,模板和可调用对象的合作使得算法函数可以被生成、被定制
// 这段代码要求传入一个可调用对象,它可以接收(string, int)类型的参数
template<typename binaryObj>
void foo(std::string &name, int age, binaryObj obj) {
obj(name, age);
}
根据上面的分类,我们依次定义三种可调用对象
// 普通的函数
void print_name_age(std::string &name, int age) {
std::cout << "Common function: " << name << " "
<< age << std::endl;
}
// 重载了()的类
class Print {
public:
void operator()(int i) {
std::cout << "Class override (): " << i << std::endl;
}
void operator()(std::string &name, int age) {
std::cout << "Class override (): " << name << " "
<< age << std::endl;
}
};
int main() {
std::string name = "Sparrow";
int age = 21;
// 重载了()的类的实例
Print p;
foo(name, age, p);
// 普通函数对象
foo(name, age, print_name_age);
// lambda函数
foo(name, age,
[](std::string &name, int age) {
std::cout << "lambda: " << name << " " << age << std::endl;
});
}
输出:
Class override (): Sparrow 21
Common function: Sparrow 21
lambda: Sparrow 21
简单地说下这些东西存在的意义,函数不用多说;类通过重载就可以当做函数来调用,是很重要的基石——因为类可以有自己的成员变量(lambda函数捕捉列表的本质)、类对象可被当作形参和返回值(函数式编程);lambda函数的本质是匿名函数,初衷是方便程序员写代码,而如今可以在函数内部写出 auto func = lambda expression 的语句,相当于在函数中生成函数(函数式编程)
std::bind
bind函数提高了代码的复用,通过定制已有可调用对象的参数为某些固定值,生成新的可调用对象
#include <iostream>
#include <string>
#include <functional>
// 使用bind函数经常要使用占位符,_1,_2 ... _n
using namespace std::placeholders;
// 此处用普通函数作为可调用对象
void manyParams(int a, int b, std::string c) {
std::cout << "a: " << a << " "
<< "b: " << b << " "
<< "c: " << c << std::endl;
}
int main() {
// 调用p1(X) --> manyParams(X, 0, "fix")
auto p1 = std::bind(manyParams, ::_1, 0, "fix");
p1(100);
// 调用p2(X, Y) --> manyParams(Y, X, "fix")
auto p2 = std::bind(manyParams, ::_2, ::_1, "fix");
p2(1, 2);
}
输出:
a: 100 b: 0 c: fix
a: 2 b: 1 c: fix
不难猜想bind的实现原理是生成一个函数类,用成员变量记录我们给定的参数,通过包装原有函数再暴露一个最后的调用接口。既然类的成员变量可以是引用类型,那么bind函数的固定形参按理也可以绑定一个变量,这就涉及到std::ref函数。同理lambda函数的引用捕捉,因此可以说类实例是可调用对象这项技术是基石。
function类型
function类型是为了统一所有可调用对象的类型,术语“call signature”指调用对象的返回类型和形参类型特征,通过指明这个特征我们可以用funcion类型来表示类,普通函数,lambda函数
// 表示一个返回值为空,参数类型分别为int和string的可调用类型
std::function<void (int, std::string)>
很多技术可以得益于此,比如说构造函数表、生成函数等等
#include <iostream>
#include <string>
#include <functional>
// 生成函数
std::function<void (std::string&)>
generateFunc(int age) {
auto func = [age](std::string &name){
std::cout << "generate function: " <<
name << " " << age << std::endl;
};
return func;
}
int main() {
std::string name = "Sparrow";
auto func21 = generateFunc(21);
func21(name);
}
输出:
generate function: Sparrow 21