3.2 函数对象包裹器
这部分虽然属于标准库的一部分,但是从本质上来看,他却增强了 C++ 语言运行时的能力。
std::function
Lambda 表达式的本质是一个和函数对象类型相似的类型的对象(有点拗口)。就是说 Lambda 表达式实际上是一种函数对象类型,这种类型称之为闭包类型。这种闭包类型产生的对象,叫做闭包对象。
当 Lambda 表达式的捕获列表为空时,闭包对象可以转换为函数指针进行值传递。如下代码:
#include <iostream>
using namespace std;
using foo = void(int); //定义一个函数类型,前面讲过
void MyFunc(foo f)
{
f(1);
}
int main()
{
auto f = [](int value)
{
cout << value << endl;
};
MyFunc(f); //传递闭包对象,隐式转换为 foo* 函数指针
f(1); // Lambda 表达式直接调用
return 0;
}
在 C++11 中,统一了这些概念,将能够被调用的对象类型,统一称之为可调用类型。这种类型,就是通过 std::function 引入的。
std::function 是一种通用的、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作,它也是对 C++ 中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是一个函数的容器。
我们有了函数的容器之后,能更加方便的将函数、函数指针作为对象进行处理。如下代码:
#include <functional>
#include <iostream>
using namespace std;
int foo(int para)
{
return para;
}
int main()
{
function<int(int)> func = foo; //包装了一个返回值为 int,参数为 int 的函数
int imporeant = 10;
function<int(int)> func2 = [&](int value) ->int
{
return 1 + value + imporeant;
};
cout << func(10) << endl; //输出 10
cout << func2(10) << endl; //输出 21
return 0;
}
std::bind 和 std::placeholder
std::bind 用来绑定函数调用的参数的,它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数。通过这个函数,我们可以将部分调用参数提前绑定到函数身上,成为一个新的对象,然后在参数齐全后,完成调用。如下代码:
#include <iostream>
#include <functional>
using namespace std;
int foo(int a, int b, int c)
{
return a + b + c;
}
int main()
{
auto bindFoo = bind(foo, placeholders::_1, 1, 2);
//将参数 1,2 绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
cout << bindFoo(1) << endl; //输出 4,1 + 1 + 2 ==4
return 0;
}
这里书上的代码有问题,书上的 foo 函数没有返回语句。C++ 不允许一个函数有返回值但是没有返回语句。
这块内容第一章说的有歧义,现已改正。
小结
不知道大家有没有学过回调函数,我感觉函数对象包裹器就是为了解决回调函数定义和调用过于麻烦的。而且用途其实和回调函数差不多。
函数对象包裹器从底层上来说就是一种函数指针。比函数指针的范围更广,可以方便的包装成员函数和仿函数。
上面的那件事,其实传统的函数指针也能做到。指针绝对是 C/C++ 语言最神奇的部分。运用指针,我们可以对底层进行更加细节的操作,细节到调用地址。
有不少人都说,C++ 在慢慢摆脱对指针的依赖,更有甚者说 C++ 应该去除指针特性,将其全部封装。
我想说,这其实就已经背离的 C++ 的初衷——自由、高效、系统级。如果没有了指针,C++ 和那些面向对象的编程语言有什么区别。
关于面向对象这件事,在《现代 C++ 白皮书》中。C++ 之父表明了,C++ 从来不是一种面向对象的编程语言,而是一门系统级编程语言。说白了,面向对象只是 C++ 的一部分。
|