函数对象简单入门

以前没接触过函数对象这东西,前几天上网查了一下,发现很有意思,这样就可以不用函数指针了。Function object是一个对象,不过它的行为表现像函数。一般而言,它是由一个重载了operator()的类所实例化得来的对象。 Function object的涵义比通常意义上的函数更广泛,因为它可以在多次调用之间保持某种状态”——这和静态局部变量有异曲同工之妙;不过这种状态还可以被初始化,还可以从外面来检测,这可要比静态局部变量强了。虽然传递函数指针被广泛应用于事件驱动系统中,以此实现回调函数通过指针来调用。但 C++ 还是提供了另外一种可供选择的办法,即函数对象,利用它可以避免使用函数指针。这样做有几个优点。首先, 因为对象可以在内部修改而不用改动外部接口,因此设计更灵活,更富有弹性。函数对象也具备有存储先前调用结果的数据成员。此外,编译器可以内联函数对象,从而进一步增强性能。函数对象可以具体表达依赖成员模板的通用算法 , 这些算法借助普通的函数指针难以完成。写个小示例:
     

 

class Action

{

public:   

       int operator()(int i)

       {

              Act(i);

              return i;

       }

 

       virtual void Act(int i) = 0;

};

 

class Drink : public Action

{

       void Act(int i)

       {

              cout<<"No. "<<i<<" drink..."<<endl;

       }

};

 

class Eat : public Action

{

       void Act(int i)

       {

              cout<<"No. "<<i<<" eat..."<<endl;

       }    

};

 

class TestAction

{

public:

       void TestAct(int i, Action& testAct)

       {    

              testAct(i);

       }

};

 

int main(void)

{           

       TestAction doact;              

       doact.TestAct(0, Drink());

       doact.TestAct(1, Drink());

       doact.TestAct(2, Drink());  

       doact.TestAct(0, Eat());

       doact.TestAct(1, Eat());

       doact.TestAct(2, Eat());

       return 0;

}

 

 

尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。函数对象(也称算符)是重载了“()”操作符的普通类对象。因此从语法上讲,函数对象与普通的函数行为类似。

用函数对象代替函数指针有几个优点,首先,因为对象可以在内部修改而不用改动外部接口,因此设计更灵活,更富有弹性。函数对象也具备有存储先前调用结果的数据成员。在使用普通函数时需要将先前调用的结果存储在全程或者本地静态变量中,但是全程或者本地静态变量有某些我们不愿意看到的缺陷。

其次,在函数对象中编译器能实现内联调用,从而更进一步增强了性能。这在函数指针中几乎是不可能实现的。

下面举例说明如何定义和使用函数对象。首先,声明一个普通的类并重载“()”操作符:

class Negate

{

public:

int operator() (int n) { return -n;}

};

重载操作语句中,记住第一个圆括弧总是空的,因为它代表重载的操作符名;第二个圆括弧是参数列表。一般在重载操作符时,参数数量是固定的,而重载“()”操作符时有所不同,它可以有任意多个参数。

因为在Negate中内建的操作是一元的(只有一个操作数),重载的“()”操作符也只有一个参数。返回类型与参数类型相同-本例中为int。函数返回与参数符号相反的整数。

使用函数对象

我们现在定义一个叫Callback()的函数来测试函数对象。Callback()有两个参数:一个为int一个是对类Negate的引用。Callback()将函数对象neg作为一个普通的函数名:

void Callback(int n, Negate & neg)

{

int val = neg(n); //调用重载的操作符“()”

cout << val;

}

不要的代码中,注意neg是对象,而不是函数。编译器将语句

int val = neg(n);

转化为

int val = neg.operator()(n);

通常,函数对象不定义构造函数和析构函数。因此,在创建和销毁过程中就不会发生任何问题。前面曾提到过,编译器能内联重载的操作符代码,所以就避免了与函数调用相关的运行时问题。

为了完成上面个例子,我们用主函数main()实现Callback()的参数传递:

int main(void)

{

Callback(5, Negate() ); //输出 -5

}

本例传递整数5和一个临时Negate对象到Callback(),然后程序输出-5

模板函数对象

从上面的例子中可以看出,其数据类型被限制在int,而通用性是函数对象的优势之一,如何创建具有通用性的函数对象呢?方法是使用模板,也就是将重载的操作符()定义为类成员模板,以便函数对象适用于任何数据类型:如double_int64char

class GenericNegate

{

public:

template T operator() (T t) const {return -t;}

};

int main(void)

{

GenericNegate negate;

cout<< negate(5.3333); // double

cout<< negate(10000000000i64); // __int64

}

如果用普通的回调函数实现上述的灵活性是相当困难的。

标准库中函数对象

C++标准库定义了几个有用的函数对象,它们可以被放到STL算法中。例如,sort()算法以判断对象(predicate object)作为其第三个参数。判断对象是一个返回Boolean型结果的模板化的函数对象。可以向sort()传递greater<>或者less<>来强行实现排序的升序或降序:

int main(void)

{

vector vi;

//..填充向量

sort(vi.begin(), vi.end(), greater() );//降序( descending )

sort(vi.begin(), vi.end(), less() ); //升序 ( ascending )

}

函数对象在表面上很复杂,难以理解。但是它有一些优点:
1
、函数对象是一种聪明的函数”(smart functions),我们可以利用它除了“()”操作符以外更多的功能。比如,它可以有自己的成员函数和成员变量,利用这一点我们可以让同一个函数对象在不同的时候有不同的行为,我们也可以在使用它之前对它进行初始化。
2
、传统的函数只能使用不同的名字来提供对不同类型参数的处理。但是,函数对象可以用相同的名字来提供对不同类型对象的处理。这也是泛型编程的特点。所以,我们使用同样的函数对象,为其初始化为不同的类型,就可以把它当作算法的操作函数使用了。例如,我们要为不同类型的容器提供排序算法,编写一个支持不同类型容器的函数对象就可以了。
3
、函数对象被编译器更好地优化了,因此使用它会带来效率的提升。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值