设为首页 加入收藏

TOP

条款18 函数对象
2013-10-07 13:16:07 来源: 作者: 【 】 浏览:54
Tags:条款 函数 对象

条款18 函数对象

经常需要一些类似于函数指针的东西,但函数指针显得笨拙、危险而且(我们不得不承认的是)过时。通常最佳方式是使用函数对象(function object)而非函数指针。

与智能指针(参见“智能指针”[条款42])一样,函数对象也是一个普通的类对象。智能指针类型重载->和*(可能还有->*)操作符,来模仿指针的行为;而函数对象类型则重载函数调用操作符(),来创建类似于函数指针的东西。考虑如下函数对象,它的每次调用都计算众所周知的斐波纳契数列(1,1,2,3,5,8,13,……)的下一个元素值:

  1. class Fib {  
  2.   public:  
  3.     Fib() : a0_(1), a1_(1) {}  
  4.     int operator ()();  
  5.   private:  
  6.     int a0_, a1_;  
  7. };  
  8. int Fib::operator ()() {  
  9.     int temp = a0_;  
  10.     a0_ = a1_;  
  11.     a1_ = temp + a0_;  
  12.     return temp;  

函数对象只是常规的类对象,但是可以采用标准的函数调用语法来调用它的operator()成员(此成员可能具有多个重载版本)。

  1. Fib fib;  
  2. //...  
  3. cout << "next two in series: " << fib()  
  4.      << ' ' << fib() << endl

fib()语法被编译器识别为对fib对象的operator()成员函数的调用,这在意思上和fib.operator()等价,但看起来更简洁。在这个例子中,使用函数对象而不使用函数或函数指针的优势在于,用于计算斐波纳契数列下一个值的状态被存储于Fib对象自身之中。如果采用函数来实现计算功能,那么必须求助于全局或局部静态变量或其他一些“卑鄙的”花招,以便在函数调用之间保持状态,或者将状态信息明确地传递给函数。还要注意的是,有别于使用静态数据的函数,我们可以拥有多个同时计算的Fib对象,这些对象的计算过程和结果不会互相干扰。

  1. int fibonacci () {  
  2.     static int a0 = 0, a1 = 1; // 有问题……  
  3.     int temp = a0;  
  4.     a0 = a1;  
  5.     a1 = temp + a0;  
  6.     return temp;  

还有可能(而且也很常见)通过创建一个带有虚拟operator()的函数对象层次结构来获得虚函数指针的效果。考虑一个数值积分软件,它用于计算曲线所包围面积的近似值,如图5所示。

一个积分函数可以对low和high之间的值反复调用一个函数,来近似计算曲线所包围的面积,这是通过计算矩形面积的总和而实现的(当然也可以采用一些类似的机制):

  1. typedef double (*F)( double );  
  2. double integrate( F f, double low, double high ) {  
  3.     const int numsteps = 8;  
  4.     double step = (high-low)/numSteps;  
  5.     double area = 0.0;  
  6.     while( low < high ) {  
  7.         area += f( low ) * step;  
  8.         low += step;  
  9.     }  
  10.     return area;  

在这个版本中,传递一个函数指针来执行所期望的积分操作。

  1. double aFunc( double x ) { ... }  
  2. //...  
  3. double area = integrate( aFunc, 0.0, 2.71828 ); 

这种方法有效果,但不灵活,因为它使用一个函数指针来指示待整合的函数,但是它不能处理需要状态的函数或指向成员函数的指针。一个替代的方式是创建一个函数对象层次结构。该层次结构的基类是一个简单接口类,只声明了一个纯虚operator()。

  1. class Func {  
  2.   public:  
  3.     virtual ~Func();  
  4.     virtual double operator ()( double ) = 0;  
  5. };  
  6. double integrate( Func &f, double low, double high ); 

现在integrate能够与任何类型的Func函数对象①(参见“多态”[条款2])进行整合。我们还注意到一个有趣之处,就是integrate函数体不需要进行任何修改(当然,重新编译一遍是免不了的),因为我们使用与调用函数指针相同的语法来调用一个函数对象。例如,可以派生出一个能处理非成员函数的Func类型:

  1. class NMFunc : public Func {  
  2.   public:  
  3.     NMFunc( double (*f)( double ) ) : f_(f) {}  
  4.     double operator ()( double d ) { return f_( d ); }  
  5.   private:  
  6.     double (*f_)( double );  
  7. }; 

这就允许整合所有最初版本的函数:

  1. double aFunc( double x ) { ... }  
  2. //...  
  3. NMFunc g( aFunc );  
  4. double area = integrate( g, 0.0, 2.71828 ); 

通过为指向成员函数的指针和类对象包装一个适当的接口,还可以将成员函数整合进来(参见“指向成员函数的指针并非指针”[条款16]):

  1. template <class C> 
  2. class MFunc : public Func {  
  3.   public:  
  4.     MFunc( C &obj, double (C::*f)(double) )  
  5.         : obj_(obj), f_(f) {}  
  6.     double operator ()( double d )  
  7.         { return (obj_.*f_)( d ); }  
  8. private:  
  9.     C &obj_;  
  10.     double (C::*f_)( double );  
  11. };  
  12. //...  
  13. AClass anObj;  
  14. MFunc<AClass> f( anObj, &AClass::aFunc );  
  15. double area = integrate( f, 0.0, 2.71828 ); 

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇条款17 处理函数和数组声明 下一篇第0条 不要拘泥于小节 (又名:了..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: