函数对象&&C++中的可调用对象

1. 函数对象

如果类重载了函数调用运算符(也就是括号运算符),则我们可以像使用函数一样使用该类的对象,我们把这样的类对象称为函数对象(function object)
例如:

class absInt
 {
 public:
	int operator() (int val) const {		//重载了函数调用运算符
		return val < 0 ? -val : val;
	}
};
int main()
{
	int i = -42;
	absInt absObj;		//含有函数调用运算符的对象
	int ui = absObj(i);		//将i传递给absObj.operator()
	return 0;
}

即使absObj只是一个对象而非函数,我们也能够“调用”该对象。调用对象实际上是在运行重载运算符,即int ui = absObj(i)等价于int ui = absObj.operaor() (i);

对比:使用函数指针的版本

int absFunc(int val) 
{
	return val<0 ? -val : val;
}

typedef int (*Abs) (int);

int main()
{
	Abs abs = absFunc;
	int i = -42;
	int ui = abs(i);
	return 0;
}

可以看出函数对象和函数指针在使用上几乎没有区别,那为什么还要使用函数对象呢?
因为函数对象作为类对象,而类可以携带其他成员(这些成员常被用于定制函数调用运算符中的操作),这样的类被称为含有状态的函数对象类
例如:

#include <iostream>
#include <string>
using namespace std;
class PrintString
{
public:
	PrintString(ostream &o = cout, char c = ' ')
		: os(o), sep(c) { }
	void operator() (const string &s) const { os << s << sep; }
private:
	ostream &os;	//私有成员,用于写入的目的流
	char sep;	//私有成员,用于存储将不同的输出string隔开的字符
};
int main()
{
	string s ;
	cin >> s;
	PrintString printer;	//使用默认值,打印到cout
	printer(s);	
	PrintString printer1(cout, '\n');	//打印到cout,后面跟一个换行符
	printer1(s);
	return 0;
}

如果使用函数指针,则只能指向其中一种类型的函数(使用空格分隔或’/n’分隔)。

另外,函数对象常常作为泛型算法的实参。例如:标准库for_each算法,使用我们自己的PrintString类来打印容器中的内容:

for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));	
//第三个实参是PrintString类的一个临时对象

2. C++中的可调用对象

C++语言中有几种可调用对象:函数、函数指针、lamba表达式、bind创建的对象以及重载了函数运算符的类
不同类型的可调用对象可能具有相同的调用形式(call signature),调用形式指明了调用的返回类型以及传递给调用的实参类型。例如:

//普通函数
int add(int i, int j) { return i+j; }
//lambda表达式,其产生一个未命名的函数对象类
auto mod = [](int i, int j) { return i % j; };
//函数对象类
struct divide {
	int operator() (int denominator, int divisor) {
		return denominator / divisor;
	}
};
//上面这些可调用对象分别对其参数执行了不同的算术运算,尽管他们的类型各不相同,
//但他们共享同一种调用形式:int (int, int)

我们如何表示这种通用调用形式?
比如我们需要定义一个函数表,用于存储指向上面那些可调用对象的指针,当程序需要执行某个操作时,可以从表中查找该调用的函数。在C++中,函数表可以很容易通过map来实现。为了构建从运算符到函数指针的映射关系,我们使用一个表示运算符符号的string作为关键字,但使用什么类型来表示指向这些可调用对象的指针呢?

有人想到了使用函数指针int (*) (int, int),并把map定义为map<string, int(*)(int, int)>,则我们可以将add添加到map中,因为add是一个指向正确类型的函数指针,但是我们不能将mod或divide存入map,因为他们不是函数指针类型而是类类型。

答案是标准库function类型
function定义在头文件<functional>中,它是C++中新定义的模板类。
function的操作如下:
function<T> f;//f是一个用来存储可调用对象的空function类对象,这些可调用对象的调用形式应该与函数类型T相同
我们可以把一个可调用对象赋给std::function对象,function类型重载了函数调用运算符(),该运算符接受实参然后将他们传递给相应的可调用对象。
例如:我们可以定义一个接受两个int、返回一个int的function类型的对象:function<int(int, int)> f

function<int (int, int)> f1 = add;	//函数指针
function<int (int, int)> f2 = divide();	//函数对象
function<int (int, int)> f3 = [] (int i, int j) { return i*j; };	//lambda
cout << f1(4,2) << endl;	//打印6
cout << f2(4,2) << endl;	//打印2
cout << f3(4,2) << endl;	//打印8

另外,使用这个function类型我们可以重新定义map,通过std::function的包裹,我们可以用统一的对象类型来传递可调用对象。我们可以把所有的可调用对象,包括函数指针、lambda表达式或者函数对象都添加到map中:

map<string, function<int (int, int)> > binops;
binops.insert( {"+", add} );	//函数指针
binops.insert( {"/",divide()} );	//函数对象
binops.insert( {"%", mod} );	//lambda表达式

当我们索引map时,将得到function类对象的引用,通过使用函数调用运算符,把实参传递给相应的可调用对象。

binops["+"](10,5);	//调用add(10, 5)
binops["/"](10, 5);	//使用divide对象调用括号运算符
binops["%"](10, 5);	//调用lambda函数对象

参考:
《C++ Primer》第5版

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值