简介
本文详细阐述了 C++ 中关于 bind 绑定器技术的基本概念和常用技巧。
引入动机
在标准算法库中,有一个算法叫 remove_if,其基本使用如下:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
bool func(int value) {
return value > 4;
}
void test() {
vector<int> number = { 1,4,7,9,5,6,4,3,2,7 };
//remove_if不会将满足条件的数据删除,但是会返回待删除元素的首地址
//然后配合 erase 使用
auto it = remove_if(number.begin(), number.end(), func);
cout << "remove_if后erase前:" << endl;
for (auto i = number.begin(); i != number.end(); ++i) {
cout << *i << " ";
}
cout << endl;
cout << "erase后:" << endl;
number.erase(it, number.end());
for (auto i = number.begin(); i != number.end(); ++i) {
cout << *i << " ";
}
cout << endl;
}
int main() {
test();
return 0;
}
运行结果如下:
对于 remove_if 来说,其第三个参数需要的是一个一元谓词,也就是上面的 func 函数。
那么我们能不能传一个二元谓词呢?
如果我们传一个二元谓词,然后将其中一个参数给固定,那么此时二元谓词不就变成了一元谓词了吗?
因此我们可以像下面这么写:
bool func2(int num1, int num2=4){
return num1 > num2;
}
我们想要实现的事情是将 number.begin() 到 number.end() 之间的数字中大于 4 的给删掉,那么意思就是将这个 begin() 和 end() 之间的数传递给函数 func2 的 num1 参数,然后 num2 参数固定下来为 4 即可。
而此时要表明这个 func2 函数要有大于符号的意思,那么就可以使用函数对象类模板 greater :
template<typename T>
class greater {
public:
bool operator()(const T& lhs, const T& rhs) const {
return lhs > rhs;
}
};
上面的 Greater 类定义了一个重载的 () 操作符,它接受两个类型为 T 的参数,并返回一个布尔值,表示第一个参数是否大于第二个参数。然后,你可以像使用 std::greater 一样使用你的 Greater 类模板来进行排序或其他需要比较函数对象的操作。
例如:
// 使用示例
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用自定义的 Greater 类模板进行降序排序
std::sort(vec.begin(), vec.end(), Greater<int>());
// 输出排序后的向量
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
补充一下函数对象类模板的概念:
那么此时就有一个问题,我们怎么让这个 4 绑定到函数的第二个参数上呢?怎么固定下来?
此时就引出了我们的 bind 绑定器的概念。
bind 1st 和 bind 2nd
Cpp_Reference 中的介绍如下:
可以看到不管是 bind 1st 还是 bind 2nd,它们的第一个参数都是 f,表示函数;而第二个参数都是 x,这个 x 的话就表示一个值。
这个值和 f 之间的关系是:如果是 bind 1st,那么 x 表示绑定到 f 函数的参数列表中的第一个值;如果是 bind 2nd,那么 x 表示绑定到 f 函数的参数列表中的第二个值。
那么通过 bind 技术,我们就可以完成绑定操作了,按照我们之前的思路,代码只要如下改即可:
void test() {
vector<int> number = { 1,4,7,9,5,6,4,3,2,7 };
//使用bind2nd函数,将4固定到greater比较器的第二个参数实现删除number中所有大于4的值
auto it = remove_if(number.begin(), number.end(), bind2nd(greater<int>(), 4));
cout << "remove_if后erase前:" << endl;
for (auto i = number.begin(); i != number.end(); ++i) {
cout << *i << " ";
}
cout << endl;
cout << "erase后:" << endl;
number.erase(it, number.end());
for (auto i = number.begin(); i != number.end(); ++i) {
cout << *i << " ";
}
cout << endl;
}
运行结果相同:
小结一下就是:remove_if 的第三个参数需要一个一元谓词,但是传进来的是二元谓词的时候,需要固定其中一个参数使得二元谓词变成一元谓词,以符合 remove_if 的传参规则,而 bind 绑定器技术可以帮助我们实现这个事情。
bind
上面的 bind1st 和 bind2nd 的用法还比较机械,且只能绑定一个二元的函数,即函数参数为 2 的函数。
对于 n 元函数这俩就傻眼了,因此我们还有终极大招:bind !
这才是本文的重点,最终的绑定器技术:
另外对比上刚刚的 bind1st 和 bind2nd 的 cpp_reference 中的截图不难发现,只有 bind 是从 C++11 标准中提供并且到现在依然还在使用的,而 bind1st 和 bind2nd 已经过时,甚至在 C++17 中已经被抛弃了。
可以看到其第一个参数依然是一个 f 函数,但是第二个参数则是可变参数了,意味着数量可能有多个。
因此此时的 bind 就可以绑定 n 元函数了。
另外还应该注意到的一点是 bind 函数的返回类型是没有明确的 unspecified ,因此如果我们要接收该函数的返回值,应该使用 auto 。
bind 作用于普通函数
接下来我们来看一下 bind 的基本使用:
#include <iostream>
#include <functional>
using namespace std;
int add(int x,int y,int z) {
cout << "int add(int, int, int)" << endl;
return x + y + z;
}
void test() {
//函数类型靠函数的返回类型和参数列表进行确定
//因此add的函数类型为:int (int, int, int)
//同时因为add函数经过bind的改造,其三个参数都分别被固定为了1,2,3
//因此 f 的函数类型其实就为:int ()
//即返回类型为 int,但是参数列表为空
auto f = bind(add, 1, 2, 3);
//调用函数 f
cout << "f() = " << f() << endl;
}
int main() {
test();
return 0;
}
上面这是 bind 作用于普通的函数使用。
bind 作用于类的成员函数
#include <iostream>
#include <functional>
using namespace std;
class Example {
public:
int add(int x, int y) {
cout << "int Example::add(int, int)" << endl;
return x + y;
}
};
void test() {
/*
* 对于类成员函数使用 bind 时需要注意下面几点:
* 1、因为普通函数的函数名就是入口地址,因此加不加&都可以
* 但是对于类成员函数则必须要加取地址符号&
* 2、对于类成员函数来说,因为我们引用的不是静态成员函数而是普通成员函数,
* 因此其第一个参数是隐含的this指针,在绑定时我们需要将这个this指针
* 作为第一个参数一并传入
*/
//Example中的add函数类型为:int (this, int, int)
//那么 f 经过 bind 过后函数类型为:int ()
Example ex;
auto f = bind(&Example::add, &ex, 20, 39);
cout << "f() = " << f() << endl;
}
int main() {
test();
return 0;
}
而这种用法,很像函数指针!
bind 与函数指针
直接看代码:
int func1(int x) {
cout << "int fun1(int) " << endl;
return x;
}
int func2(int y) {
cout << "int fun2(int) " << endl;
return y;
}
void test2() {
//函数类型为 int (int),int 表示返回值,(int) 表示接收一个参数int
//此时我们需要一个指针来指向这种类型的函数,因此用 *pFunc
//此时的 *pFunc 就是函数指针
//顺便提一下指针函数指的是返回值类型是指针类型的函数
//比如 int* pf(int=0){}
int (*pFunc)(int);
//让函数指针 pFunc 指向 func1 函数
//func1 是普通函数,因此函数名就是入口地址,可以不写&
pFunc = &func1;
//现在我们想把 pFunc 做成返回值类型是int,参数也是int 的这样一种类型怎么办呢?
//使用重定义即可,这时候 pFunc 就是一种类型了
//实际上现在的语法像下面这样写会更好懂一点,
//其实是应该像这样的:typedef int(int) pFunc1;
//这就表示 pFunc1 是一种形似 int(int) 的函数类型
typedef int (*pFunc1)(int);
//因为 pFunc1 是一种类型,所以我们可以用它创建对象
pFunc1 f = &func1;//注册回调函数(这是函数指针一个经典的用法)
cout << "f(10)=" << f(10) << endl;//回调函数的调用(这是函数指针一个经典的用法)
f = func2;
cout << "f(20)=" << f(20) << endl;
}
int main() {
test2();
return 0;
}
对比之前所使用的 bind 技术,可以发现二者有诸多相同之处,这里就不再赘述。
bind 中的 placeholders 与 cref()
对于函数指针,当我们将它声明完的那一刻,它就已经固定了类型无法更改,不够灵活。
比如之前的代码中:
typedef int (*pFunc1)(int);
pFunc1 f = &func1;
cout << "f(10)=" << f(10) << endl;
我们不再让 f 指向 func1 ,而是指向下面的函数的话:
int add(int x, int y, int z){
return x+y+z;
}
肯定会报错,因为类型都不匹配。
但是我们使用 bind 则可以解决其灵活性不足的问题:
#include <iostream>
#include <functional>
using namespace std;
int add(int x, int y, int z) {
return x + y + z;
}
int func1(int x) {
cout << "int fun1(int) " << endl;
return x;
}
int func2(int y) {
cout << "int fun2(int) " << endl;
return y;
}
void test2() {
typedef int (*pFunc)(int);
pFunc f = &func1;
cout << "f(10)=" << f(10) << endl;
f = func2;
cout << "f(20)=" << f(20) << endl;
//f = add; ;类型不匹配,报错
//使用 bind 来解决
//add的函数类型为:int(int,int,int)
//bind过后f3的函数类型为:int(int,int)
/*
* 对于add的第一个参数,我们使用了 100 来绑定
* 对于第二个和第三个参数我们则是采用了placeholders占位符先占位
*/
auto f3 = bind(&add, 100, placeholders::_1, placeholders::_2);
cout << "f3() = " << f3(20,30) << endl;
}
int main() {
test2();
return 0;
}
运行结果如下:
可以发现,bind 比函数指针要灵活的多,函数指针只能去指向某种固定类型的函数类型,但是 bind 可以去绑定一个函数,这个函数不管有多少参数我可以绑定其中一个,绑定其中两个…等随意数量的绑定。
另外,假如我们传入了额外的参数会发现也并不会报错:
cout << "f3() = " << f3(20,30,40,50,832,28,48) << endl;
为什么不会报错?众多参数之中又是如何进行选取的?
这都是由于我们在 bind 中所采用的 placeholders 占位符所决定的:
auto f3 = bind(&add, 100, placeholders::_1, placeholders::_2);
placeholders 就是占位符,而后面跟的下划线数字最多可以传到 _29 。
占位符本身代表的是形参的位置,而占位符后的下划线数字则代表的是实参的位置。
以上面的代码为例的话,因为调用 f3 函数时传入的顺序前两个是 20 和 30,而在 bind 时在第二和第三个实参位置采取的是 placeholders::_1 和 placeholders::_2 ,因此最后 f3 采用的就是前两个值 20 和 30,多余的就看不到了 。
这就是占位符和占位符后面数字所代表的含义。
可以再来一个例子感受一下:
void func3(int x1, int x2, int x3, const int& x4, int x5) {
cout << "x1 = " << x1 << endl
<< "x2 = " << x2 << endl
<< "x3 = " << x3 << endl
<< "x4 = " << x4 << endl
<< "x5 = " << x5 << endl;
}
void test3() {
//占位符本身代表的是形参的位置
//占位符中的数字代表的是实参的位置
//另外 bind 中默认使用的是值传递
int number = 100;
auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
number, number);
number = 700;
f(20, 40, 300, 200, 600, 800);
}
int main() {
test3();
return 0;
}
但是上面的代码中有一个问题是我们的第四个参数是使用 const 引用传入的,按道理我们的 number 值发生该变那么打印出来的 x4 的值也应该发生改变(从 100 变成 700),但上面没有发生这样的变化,这是因为 bind 在绑定值的时候默认使用的是值传递。
为了使得引用传递生效,需要使用 cref() 函数:
void test3() {
//占位符本身代表的是形参的位置
//占位符中的数字代表的是实参的位置
//另外 bind 中默认使用的是值传递
int number = 100;
//可以使用 cref() 函数将传入参数按 const 引用类型传入
auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
cref(number), number);
number = 700;
f(20, 40, 300, 200, 600, 800);
}
运行结果如下:
此时就发生了改变,这个 cref() 函数被称为引用包装器。
如果只是普通的引用的话,那么可以选择使用 ref() ,使用方式同上,不再赘述。
function
在前面的学习中,我们现在已经知道了 bind 的使用方法。
auto f = bind(&func3, 1, placeholders::_1, placeholders::_6,
cref(number), number);
但是有一个问题,之前我们都是自己从逻辑上推理出的 bind 函数的返回类型,但是我们知道了函数的返回类型我们能怎么进行操作呢?意思是怎么用一种特殊的形式来写出 auto 所代表的类型?
这就需要用到 function:
乍一看,似乎不太好明白这个 function 的声明是在干嘛、是如何使用的,但其实我们可以类比 vector 的声明形式一起看,因为它俩是差不多的:
template<class>
class function; /* undefined */
// 下面是 function 的声明
template< class R, class... Args>
class function<R(Args...)>
//下面是 vector 的声明
template<class T, class Allocator = std::allocator<T>>
class vector;
这样看起来是不是挺像的?
那么来看一下我们的 vector 是怎么使用的:
vector<int> number;
那我们就可以依葫芦画瓢,写出相同的 function 的使用方法:
function<类型> func;
那么现在关键就是看这个类型究竟如何定义的,在 function 的声明中可以看到其定义如下:
template< class R, class... Args>
class function<R(Args...)>
R(Args…) 都是来自模板参数提供,而 R 其实就是类型,Args… 实际上就是参数,因此我们就可以如下使用:
function<int(int,int,int)> func;
是不是很眼熟?int(int,int,int) 这不就是我们的函数类型吗?
因此 function 就相当于一个容器,专门用来存放某一种函数类型的数据,即可以存放一个一个相同函数类型的函数。
通过 function 接收 bind 函数返回值类型
通过 function,我们就可以写出之前使用 auto 来隐藏的具体的 bind 函数的返回类型是什么了:
#include <iostream>
#include <functional>
using namespace std;
int add(int x, int y, int z) {
cout << "int add(int, int, int)" << endl;
return x + y + z;
}
void test() {
//因为add的三个参数都被绑定了,因此f的函数类型为int()
function<int()> f = bind(add, 1, 2, 3);
cout << "f() = " << f() << endl;
//像下面这样写会报错,因为没有绑定参数的位置应该提供占位符而不能直接空着
//function<int(int,int)> f2 = bind(add, 1); 报错
function<int(int, int)> f2 = bind(add, 1, placeholders::_1, placeholders::_2);
cout << "f2() = " << f2(23,40) << endl;
}
int main() {
test();
return 0;
}
bind 作用到类数据成员上
之前都提过,bind 可以绑定到普通函数(如 add)上面来,可以绑定到类成员函数(如 Example::add)上面来,甚至我们也可以让 bind 绑定到一个类的数据成员上面来,来试一下:
class Example {
public:
int add(int x, int y) {
cout << "int Example::add(int, int)" << endl;
return x + y;
}
//data 是 Example 类中的一个数据成员
int data = 200;
};
我们将思维转换一下,data 其实可以看作是一个函数,看成返回类型是 int,参数是一个无参的这样一个函数,因此同样可以使用 bind 绑定:
#include <iostream>
#include <functional>
using namespace std;
class Example {
public:
int add(int x, int y) {
cout << "int Example::add(int, int)" << endl;
return x + y;
}
//data 是 Example 类中的一个数据成员
int data = 200;
};
void test() {
function<int()> f = bind(add, 1, 2, 3);
cout << "f() = " << f() << endl;
Example ex;
f = bind(&Example::data, &ex);
cout << "类数据成员:f() = " << f() << endl;
}
int main() {
test();
return 0;
}
因此可以得出结论,bind 可以绑定到 n 元函数,该函数可以是自由函数、全局函数(非成员函数),也可以绑定到成员函数,甚至可以绑定到数据成员上。
同时我们也清楚了 bind 是可以改变函数的形态的(改变函数的类型),函数的类型包括函数的返回类型,以及函数的参数列表(即函数参数的个数,函数参数的顺序,函数参数的类型)。
而 function 呢就是用来接收函数的类型,function 是函数的容器。
最后,通过 bind 和 function 组合使用可以实现多态技术。
bind + function 实现多态技术
先看一段多态技术的示例代码:
#include <iostream>
#include <math.h>
#include <functional>
using namespace std;
class Figure {
public:
virtual void display() = 0;
virtual double area() = 0;
};
class Rectangle: public Figure {
public:
Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {
}
void display() override {
cout << "Rectangel";
}
double area() override {
return _length * _width;
}
private:
double _length;
double _width;
};
class Circle : public Figure {
public:
Circle(double radius=0) :_radius(radius) {
}
void display() override {
cout << "Circle";
}
double area() override {
return 3.14 * _radius * _radius;
}
private:
double _radius;
};
class Triangle : public Figure {
public:
Triangle(double a = 0, double b = 0, double c = 0)
:_a(a),
_b(b),
_c(c) {
}
void display() override {
cout << "Triangel";
}
double area() override {
double tmp = (_a + _b + _c) / 2;
return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
}
private:
double _a;
double _b;
double _c;
};
void func(Figure* pFig) {
pFig->display();
cout << "的面积为 : " << pFig->area() << endl;
}
int main() {
Rectangle rectangle(10, 20);
Circle circle(10);
Triangle triangle(3, 4, 5);
func(&rectangle);
func(&circle);
func(&triangle);
return 0;
}
运行结果如下:
那么接下来我们就使用 bind + function 的方式来实现这样的多态技术。
因为与继承无关了,因此我们需要删除掉上面示例代码中关于继承的内容:
#include <iostream>
#include <math.h>
#include <functional>
using namespace std;
class Figure {
public:
virtual void display() = 0;
virtual double area() = 0;
};
class Rectangle{
public:
Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {
}
void display() {
cout << "Rectangel";
}
double area() {
return _length * _width;
}
private:
double _length;
double _width;
};
class Circle{
public:
Circle(double radius=0) :_radius(radius) {
}
void display() {
cout << "Circle";
}
double area() {
return 3.14 * _radius * _radius;
}
private:
double _radius;
};
class Triangle{
public:
Triangle(double a = 0, double b = 0, double c = 0)
:_a(a),
_b(b),
_c(c) {
}
void display() {
cout << "Triangel";
}
double area() {
double tmp = (_a + _b + _c) / 2;
return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
}
private:
double _a;
double _b;
double _c;
};
void func(Figure* pFig) {
pFig->display();
cout << "的面积为 : " << pFig->area() << endl;
}
int main() {
Rectangle rectangle(10, 20);
Circle circle(10);
Triangle triangle(3, 4, 5);
func(&rectangle);
func(&circle);
func(&triangle);
return 0;
}
在 Figure 类中,其有两个函数 display 和 area,这个类本身作为接口存在由其它的类来实现该接口。
这两个函数的参数都为void,只有返回值不同,我们先将它们取个别名:
#include <iostream>
#include <math.h>
#include <functional>
using namespace std;
class Figure {
public:
//给这两个函数类型起别名
using DisplayCallback = function<void()>;
using AreaCallback = function<double()>;
//根据起的别名创建对象
DisplayCallback _displayCallback;
AreaCallback _areaCallback;
//注册回调函数,采用右值接收的原因是为了避免拷贝
//相当于:void setDisplayCallback(function<void()>&& cb){}
void setDisplayCallback(DisplayCallback&& cb) {
//这里可以用move也可以不用move,但是使用move可以避免拷贝操作
//因为这里是两个对象间的赋值运算,势必可能会用到赋值拷贝
//因此使用move可以尽可能避免拷贝操作,增加效率
_displayCallback = move(cb);
}
//注册回调函数
void setAreaCallback(AreaCallback&& cb) {
_areaCallback = cb;
}
//执行回调函数
void handlerDisplayCallback() const {
if (_displayCallback) {
_displayCallback();
}
}
double handlerAreaCallback() const {
if (_areaCallback) {
//因为AreaCallback的函数类型返回值是double
return _areaCallback();
}
else {
return 0;
}
}
};
class Rectangle{
public:
Rectangle(double length = 0, double width = 0) :_length(length), _width(width) {
}
void display() {
cout << "Rectangel";
}
double area() {
return _length * _width;
}
private:
double _length;
double _width;
};
class Circle{
public:
Circle(double radius=0) :_radius(radius) {
}
void show() {
cout << "Circle";
}
double showArea() {
return 3.14 * _radius * _radius;
}
private:
double _radius;
};
class Triangle{
public:
Triangle(double a = 0, double b = 0, double c = 0)
:_a(a),
_b(b),
_c(c) {
}
void print(int x) {
cout << "Triangel";
}
double printArea() {
double tmp = (_a + _b + _c) / 2;
return sqrt(tmp * (tmp - _a) * (tmp - _b) * (tmp - _c));
}
private:
double _a;
double _b;
double _c;
};
void func(const Figure& pFig) {
//执行回调函数
pFig.handlerDisplayCallback();
cout << "的面积为 : " << pFig.handlerAreaCallback() << endl;
}
int main() {
Rectangle rectangle(10, 20);
Circle circle(10);
Triangle triangle(3, 4, 5);
Figure fig;
//回调函数的注册
//此时bind函数返回的类型正好就是setDisplayCallback函数所需要的function类型
fig.setDisplayCallback(bind(&Rectangle::display,&rectangle));
fig.setAreaCallback(bind(&Rectangle::area, &rectangle));
func(fig);
fig.setDisplayCallback(bind(&Circle::show, &circle));
fig.setAreaCallback(bind(&Circle ::showArea, &circle));
func(fig);
//传入一个参数也一样没有问题,只要符合函数类型即可
fig.setDisplayCallback(bind(&Triangle::print, &triangle, 3));
fig.setAreaCallback(bind(&Triangle::printArea, &triangle));
func(fig);
return 0;
}
运行结果如下:
可以看见一样可以实现多态技术。
之前使用继承和虚函数的方法被称为面向对象的方法,而 bind+function 的这一种方法叫基于对象的方法(没有使用到继承那就是基于对象的方法)。
补充成员函数绑定器:mem_fn() 函数
最后再补充一个 mem_fn() 函数的知识点。
直接上代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
class Number {
public:
Number(size_t data = 0):_data(data) {
}
void print() const {
cout << _data << " ";
}
//判断是否是一个偶数
bool isEven() const {
return (0 == _data % 2);
}
bool isPrime() const {
if (1 == _data) {
return false;
}
//是否是质数/素数
for (size_t idx = 2; idx != _data / 2; ++idx) {
if (_data % 2 == 0) {
return false;
}
}
return true;
}
private:
size_t _data;
};
int main() {
vector<Number> vec;
for (size_t idx = 0; idx != 30; ++idx) {
vec.push_back(Number(idx));
}
//此时的调用 print() 是会报错的,因为 print 不是一个普通函数
//而是一个成员函数
//for_each(vec.begin(), vec.end(), print); 报错
//for_each(vec.begin(), vec.end(), &Number::print); 依然报错
//对于这种情况,我们必须使用 mem_fn() 这个函数才能解决
for_each(vec.begin(), vec.end(), mem_fn(&Number::print));
return 0;
}
运行结果如下:
从上面代码我们知道了 mem_fn 的使用方法和使用场景,那么再来看一下 Cpp_Reference 上的对于 mem_fn 的介绍:
明显 mem_fn 是一个函数模板,但它的参数非常的有意思:
mem_fn(M T::* pm)
感觉分开都看得懂,但是合在一起就不太懂了。
这是因为我们不懂成员函数指针。
成员函数指针
先来看普通函数指针:
int (*pFunc)(int, int);
那么什么是成员函数指针?成员函数指针顾名思义所指向的是属于一个类里面的成员函数。
那为什么会有这个成员函数指针的概念呢?
因为大家会发现,只要函数是属于类里面的成员函数的话,那这个时候成员函数则肯定会有一个 this 指针作为该成员函数的第一个也是隐含的参数。因此针对于类的成员函数,有一个特定的概念叫成员函数指针:
class Test{
public:
int add(int x, int y){
}
};
//对于非静态的成员函数,都会在第一个参数的位置隐藏一个this指针
//因此会有一个成员函数指针如下专门用来解决 this 指针的问题
int (Test::*pFunc)(int, int);
//此时使用成员函数指针去指向成员函数 add 就没有问题了
pFunc = &Test::add;
仔细看成员函数指针的形式,就会发现和我们的 mem_fn 的参数形式差不多,这也就是为什么我们的代码能有效运行的原因:
for_each(vec.begin(), vec.end(), mem_fn(&Number::print));