Is it at all possible to erase from a vector with

2019-08-06 09:06发布

问题:

Alright. For the sake of other (more simple but not explanatory enough) questions that this might look like, I am not asking if this is possible or impossible (because I found that out already), I am asking if there is a lighter alternative to my question.

What I have is what would be considered a main class, and in that main class, there is a variable that references to a 'World Map' class. In essence, this 'WorldMap' class is a container of other class variables. The main class does all of the looping and updates all of the respective objects that are active. There are times in this loop that I need to delete an object of a vector that is deep inside a recursive set of containers (As shown in the code provided). It would be extremely tedious to repeatedly have to reference the necessary variable as a pointer to another pointer (and so on) to point to the specific object I need, and later erase it (this was the concept I used before switching to C++11) so instead I have a range for loop (also shown in the code). My example code shows the idea that I have in place, where I want to cut down on the tedium as well as make the code a lot more readable.

This is the example code:

struct item{
    int stat;
};

struct character{
    int otherStat;
    std::vector<item> myItems;
};

struct charContainer{
    std::map<int, character> myChars;
};

int main(){
    //...
    charContainer box;
    //I want to do something closer to this
    for(item targItem: box.myChars[iter].myItems){
        //Then I only have to use targItem as the reference
        if(targItem.isFinished)
            box.myChars[iter].myItems.erase(targItem);
    }
    //Instead of doing this
    for(int a=0;a<box.myChars[iter].myItems.size();a++){
        //Then I have to repeatedly use box.myChars[iter].myItems[a]
        if(box.myChars[iter].myItems[a].isFinished)
            box.myChars[iter].myItems.erase(box.myChars[iter].myItems[a]);
    }
}

TLDR: I want to remove the tedium of repeatedly calling the full reference by using the new range for loops shown in C++11.

EDIT: I am not trying to delete the elements all at once. I am asking how I would delete them in the matter of the first loop. I am deleting them when I am done with them externally (via an if statement). How would I delete specific elements, NOT all of them?

回答1:

If you simply want to clear an std::vector, there is a very simple method you can use:

std::vector<item> v;

// Fill v with elements...

v.clear(); // Removes all elements from v.

In addition to this, I'd like to point out that [1] to erase an element in a vector requires the usage of iterators, and [2] even if your approach was allowed, erasing elements from a vector inside a for loop is a bad idea if you are not careful. Suppose your vector has 5 elements:

std::vector<int> v = { 1, 2, 3, 4, 5 };

Then your loop would have the following effect:

  • First iteration: a == 0, size() == 5. We remove the first element, then the vector will contain {2, 3, 4, 5}

  • Second iteration: a == 1, size() == 4. We then remove the second element, then the vector will contain {2,4,5}

  • Third iteration: a == 2, size() == 3. We remove the third element, and we are left with the final result {2,4}.

Since this does not actually empty the vector, I suppose it is not what you were looking for.

If instead you have some particular condition that you want to apply to remove the elements, it is very easily applied in C++11 in the following way:

std::vector<MyType> v = { /* initialize vector */ };

// The following is a lambda, which is a function you can store in a variable.
// Here we use it to represent the condition that should be used to remove
// elements from the vector v.
auto isToRemove = [](const MyType & value){ 
    return /* true if to remove, false if not */ 
};

// A vector can remove multiple elements at the same time using its method erase().
// Erase will remove all elements within a specified range. We use this method 
// together with another method provided by the standard library: remove_if.
// What it does is it deletes all elements for which a particular predicate 
// returns true within a range, and leaves the empty spaces at the end.
v.erase( std::remove_if( std::begin(v), std::end(v), isToRemove ), std::end(v) );

// Done!


回答2:

I am deleting them when I am done with them externally (via an if statement). How would I delete specific elements, NOT all of them?

In my opinion, you're looking at this the wrong way. Writing loops to delete items from a sequence container is always problematic and not recommended. Strive to stay away from removing items in this fashion.

When you work with containers, you should strategically set up your code so that you place the deleted or "about to be deleted" items in a part of the container that is easily accessed, away from the items in the container that you do not want to delete. At the time you actually do want to remove them, you know where they are and thus can call some function to expel them from the container.

One answer was already given, and that is to use the erase-remove(if) idiom. When you call remove or remove_if, the items that are "bad" are moved to the end of the container. The return value for remove(_if) is the iterator to the start of the items that will be removed. Then you feed this iterator to the vector::erase method to delete these items permanently from the container.

The other solution (but probably less used) is the std::partition algorithm. The std::partition also can move the "bad" items to the end of the container, but unlike remove(_if), the items are still valid (i.e. you can leave them at the end of the container and still use them safely). Then later on, you can remove them as you wish in a separate step since std::partition also returns an iterator.



回答3:

Why not have a standard iterator iterating over a vector. That way you can delete the element by passing an iterator. Then .erase() will return the next available iterator. And if your next iterator is iterator::end() then your loop will exit.