第二种实现策略就是使用我们的函子
函子就是 function object
有时候也称函数对象 或者functor
我们称之为函子 函子的目的
在功能上就类似于函数指针
而在实现上呢
它是重载了函数调用操作符的一个类
同样地 当你想用它去比较
两个对象大小关系的时候
你必须确保你要比较的那两个对象
它们所从属的类上具有小于操作符
比较大小关系嘛
缺省的时候比较从小到大
需要一个小于比较操作符
你必须重载它 如果没有 你必须重载它
函子有一些特殊的优点
函数指针本身是不能够内联的
而函子可以
因为有的时候函子的比较
实际上非常非常简单
所以代码很简单
使用内联 整个程序运行效率能够提升
函数指针不能内联 而函子可以
这样的话 效率更高
第二 函子本身可以带有任意数量、任意型式的额外数据
可以保存我们的结果和状态
中间结果、最后的结果都可以
可以记录它的状态变化
我们代码的质量控制性、灵活性有了大大的提升
第三个 编译时可以对函子进行型式检查
这个也是相当重要的一个地方
你说函数指针型它能不能够检查呢
它当然也能够检查
但是函子 它所能做的事情
显然要比使用函数指针所能做的事情
要多得多 也灵活得多
我们来看这道题怎么样用函子来实现
我们首先给出
函子的实现以后的使用方法
如果我有这样一个数组
当我想调用刚才那个 Min() 函数
求它的最小的元的时候
那么你就可以调用 Min()
第一个参数传数组的基地址
第二个参数传元素的个数
第三个传一个函子对象
因为这个函子对象仅仅用于
我们 Min() 函数的这次调用
所以我们就构造一个
匿名的函子对象就够了
不需要再给它起一个专门的名字了
那么我们就在这个函子类上
直接调用它的缺省构造函数
构造一个就可以了
这个函子类名字叫什么呢
叫 Comparer〈int〉 后面跟着一个“()”
就构造一个函子类的匿名对象
专门来处理整数比较的 就这个意思
我们现在来看我们的 Comparer〈int〉怎么实现
定义一个类模板
template〈typename T〉 class Comparer
｛ public: bool operate()( const T & a, const T & b )｛ return a < b; ｝ ｝;
这就是 Comparer 这个类
当然它是个类模板
我们待会才会讨论类模板
现在你看到的就是我们在这个类里边
我们重载了一个函数调用的操作符
它比较 a、b 两者的关系
当 a 小于 b 的时候 返回 true
否则返回 false
注意到这里边有一个非常重要的地方
就是它使用小于操作符来比较
a 和 b 这两个对象的大小关系
如果 a 和 b 这两个对象所从属的类是你自己定义的
那么你要想使用小于操作符
你必须保证这个类上重载了小于操作符
否则它怎么调用小于操作符啊 对不对
所以你必须确保 a 和 b 所从属的那个类
也就是 T 这个型上有小于操作符
如果没有 这段代码是不能工作的
有了它 我们可以修改 Min() 函数
template〈typename T, typename Comparer〉
Comparer 这时候它是一个型的名字了
我们就使用一个比较对象
这样一个型 Comparer
我们的函子来作为模板的第二个型式参数
在内部 像函数一样使用这个 Comparer 对象
传进来的是一个对象
我们在对象上重载了函数调用操作符
所以可以让这个对象
像一个函数一样被调用
Comparer( a[i], a[index] )
出来的结果：true 和 false
就按照这个方式进行调用
这就是函数调用操作符定义或使用
就按照这个方式编写代码就可以了
接下来的这个概念就是完美转发
我们前面讲右值引用的时候
其实已经谈到过
我们说为什么 C++11 里边
要引入右值引用呢
它就是为了实现我们的移动语义
我们当时又说了
实际上没有右值引用
不也一样可以实现移动语义吗
那么右值引用的意义又何在呢
就在这里 完美转发
就是它的一个最重要的使用场合
对一个库的设计者来讲
他需要设计一个通用的函数
将接收到的参数传给其它的函数
这个过程叫参数的转发
这在库的设计上是非常有用的一个特征
但是 在没有右值引用的情况下
这个参数是没有办法完美转发的
所谓完美转发
是指所有的参数在转发以后
保持原先的语义不变
它原来是 const 转发后的结果还是 const
原来是一个引用 转发后的结果还是一个引用
原来是一个指针 转发后的结果还是一个指针
原来是个左值 转发以后还是一个左值
原来是个右值 转发以后还是一个右值
没有右值引用 这个转发是做不到完美的
也就是说 这些属性不可能全部保存下来
所以我们才有了右值转发
有了右值转发以后
因为能够完美地保存原始参数的型式特征
所以我们的构造函数事实上都不需要写那么多
我们原来讲 我写一个类
既要提供移动语义 又要提供拷贝语义
那需要写几个函数啊
4 个 俩构造函数 俩重载的赋值操作符
对吧 一个成员要写 4 个
你要记住这一点
两个成员 你就要写 8 个
非常多 组合爆炸了 没办法弄
所以有了右值引用 有了完美转发
这样的构造函数我们事实上不需要写那么多了
完美转发有一个固定的实现的流程
当你需要同时提供移动语义
与拷贝语义的时候
你要重载大量的建构函数 我们的构造函数
编程工作量大 容易出错
那么我们使用右值引用和我们的函数模板相结合
就可以实现完美转发
极大地降低我们的代码编写量
看一个例子
假设我们有一个 class A 这个类
这个类里边呢带有两个数据成员
都是 string 类型 _s，_t
我要构造它们的拷贝语义和移动语义
这里边就有讲究了
第一个字符串可能是移动语义
第二个字符串可能也是移动语义
第一个字符串移动语义
第二个字符串拷贝语义
第一个字符串拷贝语义
第二个字符串移动语义
第一个字符串拷贝语义
第二个字符串拷贝语义
你看见 是不是 4 个
构造函数要写 4 个呀
每一个都可能是移动语义、拷贝语义
你可不就要写 4 个吗
相对应地 我没有写 operate=
重载的赋值操作符
你不一样要重载 4 个吗
这里边可不就 8 个函数要写嘛
多费劲啊
有了完美转发和模板完美地配合在一起
我们只需要写一个函数模板 就 OK 了
我们写 template〈typename T1, typname T2〉 A( T1 && s, T2 && t )
初始化列表里边
_s 初始化成 std::forward〈T1〉(s)
_t 初始化为 std::forward〈T2〉(t)
构造函数一个 够了
前面要写 4 个 现在我们写一个 完事了
写了一个模板 完事了 写完了
那么当你真地在构造这个对象的时候
它会发生什么事情呢
它就会根据实际参数的型式
替你生成不同的构造函数
这个构造函数的版本
可能是一个左值引用
也可能是一个右值引用
全看你实际参数的情况
它会把它接受过来的 s 或 t
这两个形式参数
根据它的特性（它到底是一个左值呢
还是一个右值呢 是个左值引用呢
还是个右值引用呢）
根据这个特性自动地来转发给 _s 和 _t
这两个 string 对象的构造函数
做的就是这个事情
在这个例子里
T1 和 T2 本身可以是不同的型
我们这里面只不过它们都是 string
所以型刚好是相同的
这仅仅是示例
平时用的时候 并不要求它们是相同的型
当编译器进行实参推演的时候
（你真地构造这个对象 它就要做实参推演）
它会使用所谓的引用折叠机制
来处理传递的实际的参数的性质
所谓引用折叠 讲起来很复杂
同学们只需要记住一句话就行了：
当型式参数是右值引用的时候
也就是 T&& 这种型的时候
当且仅当实际参数为右值或右值引用时
传递的实际参数才是右值引用
否则传的就是左值
明白这个概念就可以了
引用折叠机制与 const、volatile
这样的关键字是无关的
你有这个东西 它就保持
没有 它就没有 注意这个概念
std::forward() 同样是标准库里面
替我们实现的一个转发函数
它就能够完成转发
它的性质和刚才那个 move() 函数非常非常类似
它转发的就是 T 的一个右值引用
不管 t 这个型是什么
它转发出来的结果就会把它转换成 T &&
就是它的一个右值引用传过去
函数内部呢 接收到一个右值引用
然后它才能决定它到底用的是右值还是左值
那是另外一回事
这是引用折叠机制
所以如果你想提供一个移动语义
那么你就传递右值引用进去
如果你想提供拷贝语义
你就传递它的一个左值引用进去
这就 OK 了