std :: remove_if删除的元素去哪儿了?

Mar*_*dik 4 c++ gcc stl

参考指出,

template< class ForwardIt, class UnaryPredicate >
ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPredicate p );
Run Code Online (Sandbox Code Playgroud)

指向范围的旧端和新端之间的元素的迭代器仍然是可解除引用的,但元素本身具有未指定的值.

我尝试了这个简单的程序,通过"未指定的值"找出它们的含义.

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

0
7
2
3
4
5
6
The program has unexpectedly finished.
Run Code Online (Sandbox Code Playgroud)

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

有趣的是,从这里可能实现

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;
}
Run Code Online (Sandbox Code Playgroud)

不会产生段错误.

这是一个错误吗?因为迭代器应该是不可分辨的.我正在使用gcc 4.7.3

Yuu*_*shi 8

首先,如果你不知道,你需要记住的东西,当你使用非常重要std::removestd::remove_if:他们实际上并不能抹去从底层容器元素.这意味着他们自己实际上并没有删除任何东西.

你需要使用像删除/擦除习语这样的东西:

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());
Run Code Online (Sandbox Code Playgroud)

"擦除"元素的作用是实现定义的.这是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;
    }
Run Code Online (Sandbox Code Playgroud)

可能导致段错误的是这个实现调用的事实_GLIBCXX_MOVE.

  • *"他们无法修改底层容器"*.事实并非如此.它们会修改容器,因为操作会导致元素重新排列.他们不能做的是减少容器的大小(即`container.size()`将在`std :: remove_if`之前和之后返回相同的值,只有一些元素(由操作删除)是*未指定的*根据C++标准). (5认同)
  • C++标准算法不适用于**容器**.它们适用于**序列**.容器是序列的一个来源,但不是唯一的. (2认同)

CB *_*ley 7

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

  • 我甚至都没想过"数据发生了什么".共享指针的值不是合同的一部分,所以我根本不关心它们.可能会移动范围的成员,这会导致空的共享指针,或者可能是"旧式"的就地破坏和复制构造而不是赋值.您应该能够在系统上找到`std :: remove_if`的源代码(因为它是一个模板)来验证这一点. (3认同)

Mic*_*urr 5

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

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

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