• 关于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 }

      

      结果就可以达到预期。

    四、总结

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

  • 相关阅读:
    struts token令牌机制
    javascript的splice()方法备注
    [转的哦】 Android字符串资源及其格式化Android 中的string.xml用法小结
    3.Android I/O文件写入和读取
    Android开发_读取联系人信息_读取通讯录号码
    ListView的美化涉及到的一些属性
    4.Android添加背景音乐的方法
    Android 网络协议
    ContentValues 和HashTable之间的区别
    5.Andorid绘图方法(Canvas)
  • 原文地址:https://www.cnblogs.com/smartNeo/p/14531946.html
Copyright © 2020-2023  润新知