关于C++标准库(第2版)std::remove_if的"特性"概述

一、背景

  在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 }

  

  结果就可以达到预期。

四、总结

  写这篇博客的原因不在于这个问题的难度和深度,而是想告诉大家,知识和技术变化的太快了,我们在阅读不是最新出版的书籍时,对一些不太理解的问题要多留心,通过官方最新的文档确认这个问题是否正确或者修改了;只有这样,我们的所学的知识和技术才不会与时代脱轨。

posted @ 2021-03-14 11:53  blackstar666  阅读(266)  评论(0编辑  收藏  举报