[C++]函数对象(一)

函数对象

定义

重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象。

class FuncOdject {
public:
    void operator()() {
        cout << "hello c++!" << endl;
    }
}
// 实例化一个对象
FuncOdject val;
val() // cout << "hello c++!" << endl;
// 这有点像使用函数
/*
void val()  
{  
    cout<<"Hello C++!"<<endl;  
}
*/  

使用函数对象的原因

虽然调用函数和使用函数对象有相同的效果,但函数对象有独特的优势,主要体现在STL的使用。

  • 1 . 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。

  • 2 . 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。比如自定义容器的排序规则。

实例

1. 使用到内部状态的函数对象

// 定义一个函数对象
class SuccessiveNumGen  
{  
public:  
    SuccessiveNumGen(int origin = 0):m_origin(origin){}  
    int operator() ()  
    {  
        return m_origin++;  
    }  
private:  
    int m_origin;  
};

vector<int> dest;
generate_n(back_inserter(dest),10,SuccessiveNumGen(3));  

此处使用generate_n来表示函数自动调用n次。

back_inserter是一个函数适配器,它返回一个函数对象,并且每次都在dest最后存放元素。

这样,最终容器中的内容为 3,4,5,6,……,12。通过使用不同的起始数字来初始化不同的函数对象,可以生成不同的数字序列。

2. 自定义容器set对字符串string的排序规则

class StringSort {
public:
    bool operator() (const string &str1, const string &str2) const
    {
        return str1 > str2;
    }
};
int main() {
    set<string,StringSort> myset;
    myset.insert("A");
    myset.insert("B");
    cout << *myset.begin() << endl;
    return 0;
}

3. 谓词函数(predicate)

class NoLess
{
public:
    NoLess(int min = 0):m_min(min){}
    bool operator() (int value) const
    {
        return value >= m_min;
    }
private:
    int m_min;
};
int main() {
    vector<int> myvec;
    myvec.push_back(1);
    myvec.push_back(2);
    myvec.push_back(10);
    remove_if(myvec.begin(), myvec.end(), NoLess(3));
    return 0;
}

find_if,remove_if都需要一个谓词函数来实现。而这种函数最好用函数对象来实现。(如果尝试调用函数来实现,会出现错误。而且并不好用,因为每次都要重新改写函数的代码来改变判断的条件。)

意外情况:

有一点需要指出的是,在调用用到函数对象的标准库算法时,除非显式地指定模板类型为“传引用”,否则默认情况下函数对象是“按值传递”的!因此,如果传递的是一个具有内部状态的函数对象,则被改变状态的是函数内部被复制的临时对象,函数结束后随之消失。真正传进来的函数对象状态并为改变。
测试如下:

class Nth  
{  
public:  
    Nth(int n=0):m_nth(n),m_count(1){}  
    bool operator() (int)  
    {  
        return m_count++ == m_nth;  
    }  

    int GetCount()const  
    {  
        return m_count;  
    }  

private:  
    int m_nth;  
    int m_count;  
};  

Nth nth(3);  
vector<int>::iterator nthItr = find_if(dest.begin(),dest.end(),nth);  
//dest内容为连续数字:3,4,5,6,……,12  
cout<<"3rd:"<<*nthItr<<endl;  
cout<<"State:"<<nth.GetCount()<<endl;  
// 输出结果为,确实能找到第三个数字(5)。
// 但查看nth的状态时,返回的m_count依然为0。说明nth确实未被修改。

一般情况下,至于是传值还是传引用,不会对我们造成太多影响。
但还是有必要清楚这一点,困为有时候针对特定的实现会出现一些莫名其妙的问题,以下面的例子来说明:

vector<int> vec;  
for(vector<int>::size_type i=0; i<10; ++i)  
    vec.push_back(i+1);  

Nth nth(3);  

remove_if(vec.begin(),vec.end(),nth);  

for(vector<int>::size_type i=0; i<10; ++i)  
{  
    cout<<vec[i]<<endl;  
}  

按正常思路来讲,删除第3个元素后输出应该是:1,2,4,5,6,7,8,9,10,10(至于最后为什么会出现两次10,这跟标准算法库设计思想有关:算法库绝不改变容器的大小!所谓的remove只是概念上的remove,被删的元素被丢到了后面,可以通过返回值来确定位置。标准算法库以后会进行说明,不是这部分的重点)。但实际情况却令人吃惊:1,2,4,5,7,8,9,10,9,10。

造成这种情况可能是因为特定的STL实现:在《The C++ Standard Library, A tutorial and reference》这本书上,作者给出一种造成这种情况的可能的实现:

template <class ForwIter, class Predicate>  
ForwIter remove_if(ForwIter beg, ForwIter end, Predicate op)  
{  
    beg = std::find_if(beg, end, op);  
    if (beg == end)   
    {  
        return beg;  
    }  
    else   
    {  
        ForwIter next = beg;  
        return remove_copy_if(++next, end, beg, op);  
    }  
}  

其中remove_copy_if:

(简单的说,就是把一个class里面不满足谓词函数的元素放进另一个class里面。)

// remove_copy_if

template <class _InputIterator, class _OutputIterator, class _Predicate>
inline _LIBCPP_INLINE_VISIBILITY
_OutputIterator
remove_copy_if(_InputIterator __first, _InputIterator __last, _OutputIterator __result, _Predicate __pred)
{
    for (; __first != __last; ++__first)
    {
        if (!__pred(*__first))
        {
            *__result = *__first;
            ++__result;
        }
    }
    return __result;
}

在这个实现中,我们清晰地看出,函数对象op两次作为参数传递到find_if和remove_copy_if函数中,正是由于标准库默认的”按值传递”,导致两次传递进来的op都是最初始的状态,即m_count为0!这样一来,传递到remove_if函数中的op将从+next位置(next是第一次找到的将被删除的第三个位置)算起再次删除第三个元素,于是第六个元素也就被删除了。

可见,不是所有的返回布尔值的函数对象都适合作为谓词函数,比如这个例子,不同标准库具体的实现会造成不同的结果。因此,作为一个好的编程习惯,用作谓词函数的函数对象,其判断结果最好不要依赖内部变动的状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值