C++ STL:Predicate vs. Function Object

版权声明:实不相瞒,我也想成为大佬 https://blog.csdn.net/CV_Jason/article/details/83963676

  所谓Predicate(判断式),就是返回Boolean值的函数或者函数对象。对STL而言,并非所有返回Boolean的函数都是合法的Predicate。这可能会导致出人意料的结果——

#include<iostream>
#include<list>
#include<algorithm>
using namespace std;

class Nth {
private:
	int n;
	int count;
public:
	Nth(int n) :n(n), count(0) {

	}
	bool operator()(int) {
		return ++count == n;
	}
};

int main(){
	list<int> coll{ 1,2,3,4,5,6,7,8,9 };
	cout << "coll:";
	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;

	auto pos = remove_if(coll.begin(), coll.end(), Nth(3));
	coll.erase(pos,coll.end());

	cout << "Removed:";
	for (auto elem : coll) {
		cout << elem << " ";
	}
	cout << endl;
	return 0;
} 

在这里插入图片描述
  结果和想象中的不太一样,按照我们所写的类,调用到第n次的时候,应该就能够得到对应的删除位置。但结果却删除了两次,第3个元素和第6个元素都被删除了,这与remove_if函数的内部构造有关——

template<typename ForwIter,typename Predicate>
ForwIter std::remove_if(ForwIter beg,ForwIter end,Predicate op)// [【1】第一处op
{
	beg = find_if(beg,end,op);// 【2】第二处op
	if(beg==end){
		return beg;
	}
	else{
		ForwIter next = beg;
		return remove_copy_if(++next,end,beg,op);// 【3】第三处op
	}
}

  从STL对remove_if函数的内部(本例基于GCC 4.9.2,其他环境不做讨论)实现不难发现问题所在,从传入的第一份op起,中间要调用一次find_if函数来找到迭代器所指向元素的位置,但第二处所调用的op是按值传递,即不改变op的原始状态,因此,在第三处op调用的时候,依旧是从零开始,那么首先在remove_copy_if内部删除了beg所指向的第一个元素,然后在后面的迭代中继续删除了第二个“第三个元素”,即第六个元素
  这种行为不能说是一种错误,C++标准并未规定Predicate是否可被算法复制。因此,为了获得C++标准保证的行为,你需要保证你所传递的function object不会因复制或调用次数而异,做到这一点,可以将operator函数声明为const成员函数,但是这样做又有些作茧自缚,毕竟很多时候还是需要改变数据成员的。

  但实际上可以不用这么麻烦,我们发现造成这一现象的罪魁祸首是迪调用了一次find_if函数,导致多计算一次pos的位置,那么不妨考虑使用while循环替代find_if的做法,Visual Studio 2017采用了这种方案——

template<class _FwdIt,
	class _Pr>
	_NODISCARD inline _FwdIt remove_if(_FwdIt _First, const _FwdIt _Last, _Pr _Pred)
	{	// remove each satisfying _Pred
	_Adl_verify_range(_First, _Last);
	auto _UFirst = _Get_unwrapped(_First);
	const auto _ULast = _Get_unwrapped(_Last);
	_UFirst = _STD find_if(_UFirst, _ULast, _Pass_fn(_Pred));
	auto _UNext = _UFirst;
	if (_UFirst != _ULast)
		{
		while (++_UFirst != _ULast)
			{
			if (!_Pred(*_UFirst))
				{
				*_UNext = _STD move(*_UFirst);
				++_UNext;
				}
			}
		}

	_Seek_wrapped(_First, _UNext);
	return (_First);
	}

  在VS2017上运行了一下,结果变得正常了。但,C++标准库应该保证绝不出现本例所出现的情况,仍在讨论之中,如果考虑程序的移植性,你应该永远不依赖于程序细节。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CV_Jason/article/details/83963676