如何使用反向迭代器调用擦除


181

我正在尝试做这样的事情:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

但是,擦除需要迭代器,而不是反向迭代器。有没有一种方法可以将反向迭代器转换为常规迭代器,或从列表中删除此元素的另一种方法?


17
顺便说一句,在编写此类循环时,请勿像在此处那样重复计算结束迭代器i != m_CursorStack.rend()。相反,写i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;。也就是说,初始化一个迭代器,您可以保留该迭代器以进行重复比较-假设最终位置不会因为循环体的副作用而改变。
SEH的

在我看来,这里最明显的问题是您为什么要这样做。反向遍历列表有什么好处?通过自己编写而不是使用此代码,您可以获得什么std::remove
杰里·科芬,

并且,在std :: list上的迭代器在删除其所引用的元素之后仍然有效吗?
史蒂夫·杰索普

3
我只想删除1个元素,因此使用“ remove”删除“ break;”,可以消除匹配时间更长且不执行我想要的任何匹配项。在这种特殊情况下,我要删除的元素几乎总是在列表的末尾或非常接近它,因此反向迭代也更快,更适合该问题。
0xC0DEFACE,2009年

4
stackoverflow.com/a/2160581/12386这表明设计人员没有明确定义实现,因为您不应该了解或关心您作为用户,但是上述@seh希望我们神奇地知道rend()是计算得出的和昂贵。
斯图

Answers:


181

经过更多的研究和测试,我找到了解决方案。显然,根据标准[24.4.1 / 1],i.base()和i之间的关系为:

&*(reverse_iterator(i)) == &*(i - 1)

(摘自Dobbs博士的文章):

替代文字

因此,在获取base()时需要应用偏移量。因此,解决方案是:

m_CursorStack.erase( --(i.base()) );

编辑

为C ++ 11更新。

reverse_iterator i保持不变:

m_CursorStack.erase( std::next(i).base() );

reverse_iterator i是高级的:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

我发现这比我以前的解决方案要清楚得多。使用任何您需要的。


27
您应该注意您引用的文章中的更多内容-表达式应具有可移植性m_CursorStack.erase( (++i).base())(伙计,使用反向迭代器执行此操作会使我头受伤...)。还应该注意的是,DDJ文章已纳入Meyer的“有效STL”书中。
Michael Burr

8
我发现该图比帮助更令人困惑。由于rbegin,ri和rend 实际上都指向绘制所指向的元素右侧的元素。该图显示了如果您要访问的元素*,但我们正在谈论的是如果您要访问的元素base,它是右侧的元素。我不喜欢--(i.base())or (++i).base()解决方案,因为它们会使迭代器发生变化。我更喜欢(i+1).base()哪个也可以。
mgiuca 2012年

4
反向迭代器是骗子。当引用时,反向迭代器将返回元素之前的元素。看到这里
bobobobo

4
只是要绝对清楚,此技术仍不能在常规for循环(迭代器以常规方式递增)中使用。参见stackoverflow.com/questions/37005449/…–
logidelic

1
m_CursorStack.erase((++ i).base())似乎是一个问题,如果++ i让您超越最后一个元素。您可以在end()上调用擦除吗?
斯图

16

请注意,m_CursorStack.erase( (++i).base())如果在for循环中使用(请参阅原始问题)可能会出现问题,因为它会更改i的值。正确的表达是m_CursorStack.erase((i+1).base())


4
您需要创建迭代器的副本并执行一下iterator j = i ; ++j,因为i+1在迭代器上不起作用,但这是正确的想法
bobobobo

3
@bobobobo,可以m_CursorStack.erase(boost::next(i).base())与Boost一起使用。或在C ++ 11m_CursorStack.erase(std::next(i).base())
alfC 2014年

12

...或从列表中删除此元素的另一种方法?

这需要-std=c++11标记(用于auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

很有魅力:)
Den-Jason

@GaetanoMendola:为什么?
slashmais '18年

3
谁保证您列表中的迭代器是有序的?
加埃塔诺·门多拉

7

有趣的是,此页面上尚无正确的解决方案。因此,以下是正确的方法:

如果使用正向迭代器,则解决方案很简单:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

对于反向迭代器,您需要执行以下操作:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

笔记:

  • 您可以构造一个 reverse_iterator从迭代器
  • 您可以使用的返回值 std::list::erase

该代码有效,但是请解释为什么使用next,以及在世界不崩溃的情况下将前向迭代器转换为反向迭代器的安全性
Lefteris E

1
@LefterisE这不是演员。它从插入器中创建一个新的反向迭代器。这是反向迭代器的常规构造函数。
西蒙·托斯(SimonTóth)

3

在使用 reverse_iteratorbase()方法并递减结果在这里有效,但值得注意的是,reverse_iterators的状态与常规iterators的状态不同。通常,出于这样的确切原因,您应该更喜欢Regular iterators reverse_iterator(以及const_iterators和const_reverse_iterators)。有关原因的详细讨论,请参见《多布斯医生杂志》。


3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}

3

这是一段代码,用于将擦除结果转换回反向迭代器,以便在反向迭代时擦除容器中的元素。有点奇怪,但是即使删除第一个或最后一个元素,它也可以工作:

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}

2

如果您不需要一路擦除所有内容,那么要解决此问题,可以使用“擦除删除”惯用语:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::remove交换容器中所有匹配pCursor到末尾的项目,并将迭代器返回到第一个匹配项。然后,erase使用范围将从第一个匹配项中删除,然后结束。不匹配元素的顺序得以保留。

如果您使用的是 std::vector,,其中内容的中间擦除可能涉及大量复制或移动。

或者,以上解释使用的答案当然reverse_iterator::base()很有趣,并且值得了解,为了解决上述确切问题,我认为这std::remove是一个更好的选择。


1

只是想澄清一下:在上面的某些评论和答案中,用于擦除的可移植版本称为(++ i).base()。但是,除非我缺少某些内容,否则正确的语句是(++ ri).base(),这意味着您将“递增” reverse_iterator(而不是迭代器)。

昨天我遇到了需要做类似事情的需要,这篇文章很有帮助。感谢大家。


0

为了补充其他人的答案,并且由于我在搜索std :: string时无意中发现了这个问题,因此没有太大的成功,下面是使用std :: string,std :: string :: erase和std :: reverse_iterator的响应

我的问题是从完整的文件名字符串中删除图像文件名。它最初是用std :: string :: find_last_of解决的,但是我研究了std :: reverse_iterator的另一种方法。

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

这使用算法,迭代器和字符串头。


0

反向迭代器很难使用。因此,只使用了通用迭代器。'r'从最后一个元素开始。当发现要擦除的东西时。擦除它并返回下一个迭代器。例如,当删除第三个元素时,它将指向当前的第四个元素。和新的第三。所以应该减少1向左移动

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.