1. 程式人生 > >C++ STL:Predicate vs. Function Object

C++ STL:Predicate vs. Function Object

  所謂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++標準庫應該保證絕不出現本例所出現的情況,仍在討論之中,如果考慮程式的移植性,你應該永遠不依賴於程式細節。
在這裡插入圖片描述