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;
}
Related
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()
);
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;
}
}
I'm currently trying to delete 2 elements from a vector if some condition is met. I can successfully remove a single element without the "vector iterator not dereferencable" error occuring, I know the problem is been caused by removing two elements at once which messes up with the Iterators but am unsure as to the correct way of removing more than one element at once.
vector<SomeObj*> objs;
vector<SomeObj*>::iterator it = objs.begin();
while (it != objs.end())
{
vector<SomeObj*>::iterator it2 = objs.begin();
bool deleted = 0;
while (it2 != objs.end())
{
if ((*it)->somecondition(**it2))
{
delete *it2;
*it2 = NULL;
it = objs.erase(it2);
delete *it;
*it = NULL;
it = objs.erase(it); //Will error here due to invalidating the iterator
deleted = 1;
break;
}
++it2;
}
if (!deleted)
++it;
}
The problem is that the first call to erase() might very well invalidate the other iterator. See this post for a quick summary of what gets invalidated when in various containers. I'd say the simplest solution is to first traverse the container and mark the entries to be erased but do not erase them, and then in a second scan just erase everything that was marked. For performance reasons in this second scan you should either use std::remove_if or use reverse iterator.
Working with nested iterators is tricky if you are mutating the container.
I've put together some sample code that does what you are wanting. What I'm doing is delaying the removal by setting the elements to be removed to nullptr and then removing those as we encounter them in the loops.
#include <iostream>
#include <vector>
class Example
{
public:
Example(int size) : size(size) {}
bool somecondition(const Example& other) const
{
return size == other.size;
}
int size;
};
int main()
{
std::vector<Example*> vec;
vec.push_back(new Example(1));
vec.push_back(new Example(2));
vec.push_back(new Example(3));
vec.push_back(new Example(2));
for (auto it1 = vec.begin(); it1 != vec.end();)
{
if (!*it1)
{
it1 = vec.erase(it1);
continue;
}
for (auto it2 = vec.begin(); it2 != vec.end(); ++it2)
{
if (!*it2)
{
vec.erase(it2);
// we need to start the outer loop again since we've invalidated its iterator
it1 = vec.begin();
break;
}
if (it1 != it2 && (*it1)->somecondition(**it2))
{
delete *it1;
*it1 = nullptr;
delete *it2;
*it2 = nullptr;
break;
}
}
++it1;
}
for (auto example : vec)
{
std::cout << example->size << std::endl;
}
return 0;
}
I have an abstract class called Object and I am using std::unordered_map<int, Object*> objects to contain these Objects within a class called DataSet. Each object has an id associated with it.
Normally, when deleting an object from my unordered_map, I can just do iterator = find(id) and then call erase on that iterator.
This is easy and efficient. The problem is, I have to implement a method to delete an entry/pair by value, rather then by the key (which was my id). This gives me the following prototype:
int DataSet::DeleteObject(Object* object)
What is the most efficient way of accomplishing this though? I'm thinking I can do something like this:
if(object){
for(auto kv : objects) {
if(kv.second == object) {
objects.erase(kv);
}
}
return 1;
}
But it seems very inefficient. So what would be the most efficient way to accomplish this?
Don't perform the lookup twice; erase via iterator:
for (auto it = m.begin(); it != m.end(); )
{
if (it->second == needle) { m.erase(it++); }
else { ++it; }
}
This deletes all occurrences of needle. If you want to erase at most the first occurrence, a simpler loop will do:
for (auto it = m.begin(); it != m.end(); ++it)
{
if (it->second == needle) { m.erase(it); break; }
}
If you want to erase exactly one element, you need to add a check that you found any needles. This can be achieved with find_if, which may also be used as a variation of the previous algorithm:
auto it = std::find_if(m.begin(), m.end(),
[&needle](const auto & p) { return p.second == needle; });
if (it != m.end()) { m.erase(it); }
else { /* no such element! */ }
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.