簡體   English   中英

從向量中刪除項目,而在 C++11 范圍內的“for”循環中?

[英]Removing item from vector, while in C++11 range 'for' loop?

我有一個 IInventory* 向量,我正在使用 C++11 范圍循環遍歷列表,以對每個列表進行處理。

在對一個做了一些事情之后,我可能想將它從列表中刪除並刪除該對象。 我知道我可以隨時在指針上調用delete來清理它,但是在for循環范圍內將它從向量中刪除的正確方法是什么? 如果我從列表中刪除它,我的循環會失效嗎?

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

for (IInventory* index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
}

不,你不能。 基於范圍的for用於當您需要訪問容器的每個元素一次時。

如果您需要在進行過程中修改容器、多次訪問元素或以其他方式以非線性方式遍歷容器,則應使用普通for循環或其同類之一。

例如:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}

每次從向量中刪除一個元素時,您必須假設被擦除元素處或之后的迭代器不再有效,因為被擦除元素之后的每個元素都被移動了。

基於范圍的 for 循環只是使用迭代器的“正常”循環的語法糖,因此上述適用。

話雖如此,您可以簡單地:

inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);

理想情況下,您不應在迭代時修改向量。 使用擦除-刪除習語。 如果這樣做,您可能會遇到一些問題。 由於在vectorerase會使從被擦除元素開始到end()所有迭代器失效,因此您需要使用以下方法確保迭代器保持有效:

for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}

請注意,您需要按原樣進行b != v.end()測試。 如果您嘗試按如下方式對其進行優化:

for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

您將遇到 UB,因為您的e在第一次erase調用后失效。

在該循環中刪除元素是否有嚴格要求? 否則,您可以將要刪除的指針設置為 NULL 並再次遍歷向量以刪除所有 NULL 指針。

std::vector<IInventory*> inv;
inv.push_back( new Foo() );
inv.push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);

對不起,如果我的 C++ 專業知識妨礙了我的回答,也很抱歉,但是如果您嘗試遍歷每個項目並進行可能的更改(例如擦除索引),請嘗試使用 backwords for 循環。

for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}

擦除索引 x 時,下一個循環將針對最后一次迭代“前面”的項目。 我真的希望這對某人有所幫助

好的,我遲到了,但無論如何:對不起,我目前閱讀的內容不正確 -可能,您只需要兩個迭代器:

std::vector<IInventory*>::iterator current = inv.begin();
for (IInventory* index : inv)
{
    if(/* ... */)
    {
        delete index;
    }
    else
    {
        *current++ = index;
    }
}
inv.erase(current, inv.end());

僅修改迭代器指向的值不會使任何其他迭代器失效,因此我們可以不必擔心。 實際上, std::remove_if (至少是 gcc 實現)做了一些非常相似的事情(使用經典循環......),只是不刪除任何內容也不擦除。

但是請注意,這不是線程安全的(!) - 但是,這也適用於上面的其他一些解決方案......

我將舉例說明,下面的例子從向量中刪除奇數元素:

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it);
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

輸出aw如下:

024
024
024

請記住,方法erase將返回傳遞的迭代器的下一個迭代器。

這里,我們可以使用更多的生成方法:

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

請參閱此處了解如何使用std::remove_if https://en.cppreference.com/w/cpp/algorithm/remove

與此主題標題相反,我將使用兩個通行證:

#include <algorithm>
#include <vector>

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

std::vector<IInventory*> toDelete;

for (IInventory* index : inv)
{
    // Do some stuff
    if (deleteConditionTrue)
    {
        toDelete.push_back(index);
    }
}

for (IInventory* index : toDelete)
{
    inv.erase(std::remove(inv.begin(), inv.end(), index), inv.end());
}

一個更優雅的解決方案是切換到std::list (假設您不需要快速隨機訪問)。

list<Widget*> widgets ; // create and use this..

然后,您可以在一行中使用.remove_if和 C++ 函子刪除:

widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;

所以在這里我只是寫了一個接受一個參數( Widget* )的函子。 返回值是從列表中刪除Widget*的條件。

我覺得這個語法很好吃。 我認為我永遠不會將remove_if用於std::vectors —— 有太多的inv.begin()inv.end()噪音,你可能最好使用基於整數索引的刪除或只是一個普通的基於常規迭代器的刪除(如下所示)。 但是無論如何你都不應該真的從std::vector中間刪除,因此建議在這種頻繁刪除列表中間的情況下切換到list

但是請注意,我沒有機會在已deleteWidget*上調用delete 要做到這一點,它看起來像這樣:

widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;

您還可以使用常規的基於迭代器的循環,如下所示:

//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}

如果你不喜歡for( list<Widget*>::iterator iter = widgets.begin() ; ...的長度,你可以使用

for( auto iter = widgets.begin() ; ...

我想我會做以下...

for (auto itr = inv.begin(); itr != inv.end();)
{
   // Do some stuff
   if (OK, I decided I need to remove this object from 'inv')
      itr = inv.erase(itr);
   else
      ++itr;
}

您無法在循環迭代期間刪除迭代器,因為迭代器計數不匹配,並且在某些迭代之后您將擁有無效的迭代器。

解決方案:1)獲取原始向量的副本 2)使用此副本迭代迭代器 2)做一些事情並將其從原始向量中刪除。

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  

一個一個的擦除元素很容易導致N^2的性能。 最好標記應該擦除的元素,並在循環后立即擦除它們。 如果我可以假定 nullptr 在您的向量中無效元素中,那么

std::vector<IInventory*> inv;
// ... push some elements to inv
for (IInventory*& index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    {
      delete index;
      index =nullptr;
    }
}
inv.erase( std::remove( begin( inv ), end( inv ), nullptr ), end( inv ) ); 

應該管用。

如果您的“做一些事情”沒有改變向量的元素並且僅用於決定刪除或保留元素,您可以將其轉換為 lambda(如某人之前的帖子中所建議的那樣)並使用

inv.erase( std::remove_if( begin( inv ), end( inv ), []( Inventory* i )
  {
    // DO some stuff
    return OK, I decided I need to remove this object from 'inv'...
  } ), end( inv ) );

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM