Remove and delete pointers that match a condition in a vector - c++

I have a std::vector that I would like to remove pointers from the vector that meet the condition isDestroyed(), but also call delete on the pointers.
I did the following, but it requires looping over the vector twice. Is there a more efficient way to do it?
std::vector<GameObject*> gameObjects;
std::vector<GameObject*> destroyedObjects;
// Get objects to be deleted
std::copy_if (gameObjects.begin(), gameObjects.end(), std::back_inserter(destroyedObjects), [](GameObject* b){return b->isDestroyed();} );
// Remove objects from vector
gameObjects.erase(
std::remove_if(
gameObjects.begin(),
gameObjects.end(),
[](GameObject* p) { return p->isDestroyed(); }
),
gameObjects.end()
);
// Delete the objects
for (GameObject* o : destroyedObjects)
delete o;

std::unique_ptr does the deletion for free:
std::vector<std::unique_ptr<GameObject>> gameObjects;
// Remove objects from vector
gameObjects.erase(
std::remove_if(
gameObjects.begin(),
gameObjects.end(),
[](const auto& p) { return p->isDestroyed(); }
),
gameObjects.end()
);
I suggest to use it instead. It also avoids mistakes like forgetting to delete or double deleting.

This should work:
std::vector<GameObject*> gameObjects;
auto end = std::stable_partition(
gameObjects.begin(),
gameObjects.end(),
[](GameObject* p) { !return p->isDestroyed(); }
);
for (auto i = end; i < gameObjects.end(); i++) {
delete *i;
}
gameObjects.erase(end, gameObjects.end());

You don't need 2 vectors. The iterator returned by std::remove_if() can be used to know which objects need to be delete'd:
std::vector<GameObject*> gameObjects;
...
auto newEnd = std::remove_if(
gameObjects.begin(), gameObjects.end(),
[](GameObject* p) { return p->isDestroyed(); }
);
for(auto iter = newEnd; iter != gameObjects.end(); ++iter) {
delete *iter;
}
gameObjects.erase(newEnd, gameObjects.end());
If you change your vector to hold std::unique_ptr<GameObject> instead of GameObject*, you don't need to delete the objects manually anymore:
std::vector<std::unique_ptr<GameObject>> gameObjects;
...
gameObjects.erase(
std::remove_if(
gameObjects.begin(), gameObjects.end(),
[](std::unique_ptr<GameObject> &p) { return p->isDestroyed(); }
),
gameObjects.end()
);

Related

C++ how to erase from vector while iterating

std::vector<int *>intList;
int *a = new int;
*a = 3;
int *b = new int;
*b = 2;
int *c = new int;
*c = 1;
intList.push_back(a);
intList.push_back(b);
intList.push_back(c);
std::vector<int *>::iterator it;
for (it = intList.begin(); it != intList.end();)
{
int *a = (int*)*it;
if ((*a) == 2)
{
delete a;
intList.erase(it);
}
else
{
++it;
}
}
This code crashes at the start of the for loop when something is erased from the vector. I do not understand why. I am using Visual Studio 2015 if that helps
erase returns next iterator.
if ((*a) == 2)
{
delete a;
it = intList.erase(it);
}
EDIT:
remove() and remove_if() will copy the elements(pointer here) and one will end up with multiple elements pointing to same integer and if you then try to free them, you'll be left with dangling pointers.
Consider the vector has 4 elements which look something like
0x196c160 0x196bec0 0x196c180 0x196bee0
One might be tempted to use erase-remove idiom
auto temp = remove_if(vec.begin(),vec.end(),[](const auto &i){return *i==2;});
Now it looks like
0x144aec0 0x144b180 0x144b180 0x144aee0
temp would be pointing to 3rd element and a
for(auto it=temp;it!=vec.end();it++)
delete *it;
Now the second element is a dangling pointer.
EDIT 2:
The above problem could be solved if you delete before the element is copied.Look at #Dietmar's answer.
Better to use std::vector<std::unique_ptr<int>> (or even std::vector<int> if you don't need pointer).
then just use erase-remove idiom:
std::vector<int> intList{3, 2, 1};
intList.erase(std::remove(intList.begin(), intList.end(), 2), intList.end());
or
std::vector<std::unique_ptr<int>> intList;
intList.puch_back(std::make_unique<int>(3));
intList.puch_back(std::make_unique<int>(2));
intList.puch_back(std::make_unique<int>(1));
intList.erase(std::remove_if(intList.begin(), intList.end(),
[](const auto& p){ return *p == 2; }),
intList.end());
If you really need raw owning pointer, you may use a variant using partition:
std::vector<int*> intList{ new int {3}, new int {2}, new int{1} };
auto it = std::partition(intList.begin(), intList.end(),
[](const auto& p){ return *p != 2; });
std::foreach (it, intList.end(), [](int* p) { delete p; });
intList.erase(it, intList.end());
Finally, if you really need to do it manually, you have to fix your erase line to:
it = intList.erase(it);
to have:
std::vector<int*> intList{ new int {3}, new int {2}, new int{1} };
for (auto it = intList.begin(); it != intList.end(); /*Empty*/) {
int *p = *it;
if (*p == 2) {
delete p;
it = intList.erase(it);
} else {
++it;
}
}
That code is causing iterator invalidation.
You're deleting a then expecting your iterator (which is just a pointer) to know what just happened.
If you have to iterate through the whole vector then consider a temporary vector of what to keep/throwaway and use that.
Or better yet just use find
http://www.cplusplus.com/reference/algorithm/find/
The simple anser is: you don’t! Instead you use a two stage approach: first get rid of the elements, then resize the container. The use of raw pointers slightly complicates things but it is still doable:
auto end = std::remove_if(intList.begin(), intList.end(),
[](int *ptr){ return *ptr == 2 && (delete ptr, true); });
intList.erase(end, endList.end());
Trying to erase individual elements while iterating over std::vector has non-linear worst case complexity.
If you take a look at documentation the erase function returns next iterator. In your case using it = intList.erase(it) is the solution. After c++11 all erase functions from other containers follow the same idea.

How to delete an object from a map which contains a vector as value in C++

I have a map which contains a of vector of type Messages.
std::map<std::string, std::vector<Message>> storage;
class Message has 3 member variables.
class Message
{
private:
std::string msg;
std::string msg_type;
int priority;
}
Now i am trying to delete an object which has priority(say 3) from the map. i am using the following function for it. But it doesn't work.
void deleteByMessagePriority(int priority)
{
if (checkPriorityOfMessage(priority))
{
for (std::map<std::string, std::vector<Message>>::iterator it = storage.begin(); it != storage.end(); it++)
{
std::vector<Message> listOfMsgs = it->second;
for (std::vector<Message>::iterator vec_it = listOfMsgs.begin(); vec_it != listOfMsgs.end(); vec_it++)
//for(int index = 0;index < listOfMsgs.size();index++)
{
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
}
}
}
}
Look carefully at this:
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
You're looking at the priority of one message (the one referred to by vec_it), but then what are you deleting if it matches?
Instead of writing your own loop, I'd use erase and std::remove_if to remove all the items you care about in that vector at once.
for (auto & item : storage) {
auto &vec = item.second;
auto start_junk = std::remove_if(
vec.begin(), vec.end(),
[=](Message const &m) { return m.priority == priority; });
vec.erase(start_junk, vec.end());
}
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
pop_back() removes the last element of the vector which you don't want.You want to check erase
Also remember erase() invalidates the iterators so you need iterator to the next element after a deleted element for which we can fortunately use return value of erase
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it); //returns iterator to the element after vec_it which can also be `listOfMsgs.end()`
std::vector<Message> listOfMsgs = it->second;
.
.
.
listOfMsgs.pop_back();
You're copying the list, only to modify the copy. What you meant is:
std::vector<Message>& listOfMsgs = it->second;
Then you can proceed erasing elements. As Gaurav Sehgal says, use the return value of erase:
std::vector<Message>::iterator vec_it = listOfMsgs.begin();
while (vec_it != listOfMsgs.end())
{
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it);
}
else
{
++vec_it;
}
}

How to erase elements from a vector of object?

I know how we can remove elements from a vector of int
std::vector<int> vec;
// .. put in some values ..
int int_to_remove = n;
vec.erase(std::remove(vec.begin(), vec.end(), int_to_remove), vec.end());
What if its a vector<obj> vec where obj is
class obj {
int ID;
string name;
}
How would I remove vectors that are holding onto a certain ID ?
std::vector<obj> vec;
// .. put in some values ..
int id_to_remove = n;
vec.erase(std::remove(vec.ID.begin(), vec.ID.end(), id_to_remove), vec.end());
Now that you are looking to delete objects matching a certain criteria, you need to use std::remove_if instead of std::remove.
vec.erase(
std::remove_if(
vec.ID.begin()
, vec.ID.end()
, [](const obj& x) {
// ID needs to be public in order for this to compile
return x.ID == id_to_remove;
}
)
, vec.end()
);

Segmentation Fault when deleting a pointer in vector

Im trying do delete a object pointer in a vector when a user wants to delete an object. Im trying to:
Delete the pointer
Remove the pointer from the vector
I have in a DataBase class a map of strings to vectors. And the vectors are containing pointers to objects. Like this:
Definition of objectMap and objectector in "database.h"
private:
typedef std::vector<Object *> objectVector;
typedef std::map<std::string, objectVector> objectMap;
objectMap objects;
Implementation of deleteId in "database.cpp"
bool DataBase::deleteId(int id)
{
// vectors
for(objectMap::iterator vec = objects.begin(); vec != objects.end(); ++vec)
{
objectVector v = vec->second;
// objects
for(objectVector::iterator obj = v.begin(); obj != v.end(); ++obj)
{
if((*obj)->getId() == id)
{
delete *obj; // pointer
*obj = 0;
v.erase(obj); // erase from vector
modified = true;
return true;
}
}
}
return false;
}
Im currently trying to the debug the program, and I am printing out the whole map of objects to see if the deletion worked. The problem is that the object pointer is still there after the deletion even if the function confirmed the deletion (i.e returned true). Can someone please explain whats going on?
objectVector v = vec->second;
This copies the vector by value and thus you're referring to the copy, not to the real vector.
Suggesting to change that line into
objectVector& v = vec->second; // Reference to the "real" vector
No problems in invalidating your obj iterator since you're quitting the function as soon as you encounter the deletion element.
Your code is wrong at least because you did not erase an element in the original vector. Use reference to the vector
for(objectMap::iterator vec = objects.begin(); vec != objects.end(); ++vec)
{
objectVector &v = vec->second;
Also consider an approach when if after the erasing of an element in a vector the vector becomes empty then maybe you shoulod also erase the corresponding element in the map.
For example )without testing)
bool DataBase::deleteId(int id)
{
bool modified = false;
for ( auto it1 = objects.begin(); !modified && it1 != objects.end(); ++it1 )
{
auto it2 = std::find_if( it1->second.begin(), it1->second.end(),
[&]( Object *o ) { return ( o->getId() == id ); } );
modified = it2 != it1->second.end();
if ( modified )
{
delete *it2;
it1->second.erase( it2 );
if ( it1->second.empty() ) it1 = objects.erase( it1 );
}
}
return modified;
}

how to find if std::deque holds given object?

I have a container<std::deque<T>> and a const T*ptr which I know points to an object in one of the contained deques. Now, I like to know (1) which deque it comes from and (2) its index in that one. How to get that info?
I'm aware that I can iterate over all objects, but there ought to be a faster solution, ought it not?
Something like (and I havent compiled this but you get the idea):
container<deque<T>> MyCont;
for( auto iter = MyCont.begin(); iter != MyCont.end(); ++iter )
{
auto foundIter = find( *iter.begin(), *iter.end(), MyObject );
if ( foundIter != *iter.end() )
{
dequeIndex = distance( *iter.begin(), foundIter );
containerIndex = distance( MyCont.begin(), iter );
break;
}
}
That's a task for a double iteration:
iterate over container
iterate over current element (a deque) and compare
#
template<typename container> std::pair<container::iterator_t, size_t> FindIndices(const container& c, const container::value_type* x) {
for(auto a = c.begin(); a != c.end(); a++)
for(auto b = a->begin(); b != a->end(); b++)
if(&*b == x)
return std::pair<container::iterator_t, size_t>(a, b-a->begin());
return std::pair<container::iterator_t, size_t>(c.end(), -1);
}
If you can instead store shared_pointers or unique_pointers in your container, you can use a std::map to efficiently retrieve the indices, as long as those don't change.
If only the queue does not change, then there is still some potential for savings.