您的位置:首页 > 编程语言 > C语言/C++

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

2016-04-18 16:42 501 查看

函数对象

定义

重载函数调用操作符的类,其对象常称为函数对象(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是第一次找到的将被删除的第三个位置)算起再次删除第三个元素,于是第六个元素也就被删除了。

可见,不是所有的返回布尔值的函数对象都适合作为谓词函数,比如这个例子,不同标准库具体的实现会造成不同的结果。因此,作为一个好的编程习惯,用作谓词函数的函数对象,其判断结果最好不要依赖内部变动的状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: