【问题标题】:Where do the elements removed by std::remove_if go?std::remove_if 删除的元素去哪了?
【发布时间】:2013-05-10 06:19:34
【问题描述】:

reference 这么说

template< class ForwardIt, class UnaryPredicate >
ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPredicate p );

指向新旧两端之间元素的迭代器 范围仍然是可取消引用的,但元素本身具有 未指定的值。

我尝试了这个简单的程序来找出“未指定值”的含义。

#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>

int main()
{
    std::vector< std::shared_ptr<int> > ints;
    for (int i = 0; i < 10; ++i)
        ints.push_back(std::make_shared<int>(i));
    std::remove_if(ints.begin(), ints.end(), 
                  [](const std::shared_ptr<int>& element)
                  {
                      return *element % 7 != 0;
                   });
    for (int i = 0; i < 10; ++i)
        std::cout << *ints[i] << std::endl;
    return 0;
}

输出是:

0
7
2
3
4
5
6
The program has unexpectedly finished.

这是在第 7 个元素之后的数据发生了一些神秘的事情,导致了段错误。

有趣的是,here 的可能实现

template<class ForwardIt, class UnaryPredicate>
ForwardIt remove_if(ForwardIt first, ForwardIt last, 
                          UnaryPredicate p)
{
    ForwardIt result = first;
    for (; first != last; ++first) {
        if (!p(*first)) {
            *result++ = *first;
        }
    }
    return result;
}

不产生段错误。

这是一个错误吗?因为迭代器应该是可解引用的。我正在使用 gcc 4.7.3

【问题讨论】:

    标签: c++ gcc stl


    【解决方案1】:

    首先,如果您不知道,在使用std::removestd::remove_if 时需要记住一些非常重要的事情:它们实际上不能从底层容器中删除元素。这意味着他们自己实际上并没有删除任何东西。

    您需要使用类似 remove/erase 的成语:

    auto to_erase = std::remove_if(ints.begin(), ints.end(), 
                  [](const std::shared_ptr<int>& element)
                  {
                      return *element % 7 != 0;
                   });
    ints.erase(to_erase, ints.end());
    

    “擦除”元素会发生什么是实现定义的。这是gcc 的实现:

      template<typename _ForwardIterator, typename _Predicate>
        _ForwardIterator
        remove_if(_ForwardIterator __first, _ForwardIterator __last,
              _Predicate __pred)
        {
          // concept requirements
          __glibcxx_function_requires(_Mutable_ForwardIteratorConcept<
                      _ForwardIterator>)
          __glibcxx_function_requires(_UnaryPredicateConcept<_Predicate,
            typename iterator_traits<_ForwardIterator>::value_type>)
          __glibcxx_requires_valid_range(__first, __last);
    
          __first = _GLIBCXX_STD_A::find_if(__first, __last, __pred);
          if(__first == __last)
            return __first;
          _ForwardIterator __result = __first;
          ++__first;
          for(; __first != __last; ++__first)
            if(!bool(__pred(*__first)))
              {
                *__result = _GLIBCXX_MOVE(*__first);
                ++__result;
              }
          return __result;
        }
    

    可能导致段错误的原因是此实现调用_GLIBCXX_MOVE

    【讨论】:

    • “他们不能修改底层容器”。那不是真的。它们确实会修改容器,因为操作会重新排列元素。他们不能做的是减小容器的大小(即container.size() 将在std::remove_if 之前和之后返回相同的值,只有一些元素(被操作删除)未指定为根据 C++ 标准)。
    • @Nawaz 我的措辞很差。我已经改写了我的答案。
    • C++ 标准算法不适用于容器。它们适用于序列。容器是序列的一种来源,但不是唯一的来源。
    • Gotcha - 小心使用 remove_iferase。如果您忘记使用此向量中的范围擦除,即。 v.erase(remove_if(...), v.end()),然后输入 v.erase(remove_if(...)) - 这只会删除第一个元素。之后你会得到无效的shared_ptrs。
    【解决方案2】:

    迭代器可能是可解引用的,但共享指针可能不是。您应该在取消引用具有未指定值的共享指针之前检查是否为空。

    【讨论】:

    • 但是您认为数据发生了什么变化?如果实现如此简单。在其实现中使用移动语义会导致这种行为吗?
    • 我什至没有想过“数据发生了什么”。共享指针的值不是合同的一部分,所以我根本不关心它们。范围的成员可能被移动,这会导致空的共享指针,或者可能是“老式”就地破坏和复制构造而不是赋值。您应该能够在您的系统上找到std::remove_if 的源代码(因为它是一个模板)来验证这一点。
    【解决方案3】:

    如果可能,C++11 编译器将使用移动语义来移动未被std::remove_if“删除”的元素。移动 shared_ptr 会使原始 shared_ptr 对象为空(它不再拥有指针) - 对原始 shared_ptr 调用 get() 将返回一个空指针。

    因此,如果您取消引用该 shared_ptr,您将获得一个空指针取消引用。

    因此,总而言之,虽然迭代器仍然是可解引用的,但 shared_ptr 可能不是。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-07
      • 1970-01-01
      • 2016-07-22
      • 2013-06-11
      • 1970-01-01
      相关资源
      最近更新 更多