一、背景
在C++标准库(第2版)第10章中的10.1.4 Predicate(判断式) VS.Function Object(函数对象)有这么个例子(gcc 4.5 visual C++2010):
1 class Nth { 2 public: 3 Nth(int nth) : _nth(nth), _count(0) {} 4 bool operator() (int) { 5 return ++_count == _nth; 6 } 7 private: 8 int _count; 9 int _nth; 10 }; 11 12 int main() 13 { 14 list<int> coll = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 15 PRINT(coll, "coll:"); 16 17 auto pos = std::remove_if(coll.begin(), coll.end(), Nth(3)); 18 coll.erase(pos, coll.end()); 19 PRINT(coll, "3rd element removed:"); 20 }
打印结构如下:
coll:1 2 3 4 5 6 7 8 9 10
3rd element removed:1 2 4 5 7 8 9 10
好家伙,原本是只删除第三个原始,直接把第6个原始也给删除了。
二、追本索根
本着好奇的心态,在http://www.cplusplus.com/reference/algorithm/remove_if/?kw=remove_if查了下文档,下面有c++98和C++11的实现源码:
C++98
1 template <class ForwardIterator, class UnaryPredicate> 2 ForwardIterator remove_if (ForwardIterator first, ForwardIterator last, 3 UnaryPredicate pred) 4 { 5 ForwardIterator result = first; 6 while (first!=last) { 7 if (!pred(*first)) { 8 if (result!=first) 9 *result = *first; 10 ++result; 11 } 12 ++first; 13 } 14 return result; 15 }
C++11:
template <class ForwardIterator, class UnaryPredicate> ForwardIterator remove_if (ForwardIterator first, ForwardIterator last, UnaryPredicate pred) { ForwardIterator result = first; while (first!=last) { if (!pred(*first)) { if (result!=first) *result = std::move(*first); // 避免拷贝 ++result; } ++first; } return result; }
用visual C++2019提供的std::remove_if(实现方法类似上面的),测试下,并没有出现C++标准库(第二版)的问题,测试代码如下:
1 class Nth { 2 public: 3 Nth(int nth) : _nth(nth), _count(0) {} 4 bool operator() (int) { 5 return ++_count == _nth; 6 } 7 private: 8 int _count; 9 int _nth; 10 }; 11 12 int main() 13 { 14 std::list<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 15 std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, " ")); 16 std::cout << std::endl; 17 auto pos = std::remove_if(vec.begin(), vec.end(), Nth(3)); 18 vec.erase(pos, vec.end()); 19 std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, " ")); 20 std::cout << std::endl; 21 }
打印结果如下:
没有问题,说明这个函数后面做了修改。当然,C++标准库(第二版)也提供了实现的源码,我也验证了下:
1 template <typename ForwardIter, typename Predicate> 2 ForwardIter _remove_if(ForwardIter beg, ForwardIter end, Predicate op) { 3 4 beg = std::find_if(beg, end, op); // 值传递 5 6 if (beg == end) { 7 return beg; 8 } 9 else { 10 auto next = beg; 11 beg = std::remove_copy_if(++next, end, beg, op); // 值传递 12 return beg; 13 } 14 }
将上面测试的std::remove_if替换程_remove_if即可,发现会导致第6个元素被删除了。问题的罪魁祸首就是令单函数对象是值传递的方式,被调函数执行返回后,该函数对象内部的值不会被修改。
三、替代方式
如果出现上面的问题,是否有解决方案呢?答案是肯定的,既然我们都知道了上面的问题是由值传递导致的,只有我们处理为引用传递就OK啦!Lambda表达式可以完美解决这个问题,只需要我们在客户端这么使用:
1 int main() 2 { 3 std::list<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 4 std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, " ")); 5 std::cout << std::endl; 6 int count = 0; 7 auto pos = _remove_if(vec.begin(), vec.end(), [&count](int element) { 8 constexpr static int kNth = 3; 9 return ++count == 3; 10 }); 11 vec.erase(pos, vec.end()); 12 std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, " ")); 13 std::cout << std::endl; 14 15 return 0; 16 }
结果就可以达到预期。
四、总结
写这篇博客的原因不在于这个问题的难度和深度,而是想告诉大家,知识和技术变化的太快了,我们在阅读不是最新出版的书籍时,对一些不太理解的问题要多留心,通过官方最新的文档确认这个问题是否正确或者修改了;只有这样,我们的所学的知识和技术才不会与时代脱轨。