Reputation: 1564
I have a class called ClassA
. This ClassA
holds a vector of pointers to dynamically allocated objects of class ClassB
. The destructor for ClassA
looks like this:
ClassA::~ClassA() {
for (vector<ClassB*>::iterator i = m_vActiveB.begin(), e = m_vActiveB.end(); i != e; ) {
vector<ClassB*>::iterator tmp(i++);
delete *tmp;
m_vActiveB.erase(tmp);
}
}
In ClassC
, I have an vector of ClassA
objects. My current scenario is this: if I don't have any ClassA
objects in my ClassC
vector, then it all works fine. But if I have objects in there, and then call .erase() on my vector in ClassC
, then an exception is thrown. Through logging I have determined that the ClassA
destructor is called (as it should be, as my understanding is that .erase()
destroys each vector member), but the exception is thrown when the loop begins.
Does anyone know what might be causing this? Thanks!
Upvotes: 3
Views: 1769
Reputation: 76280
Follow the rule of zero and just use the following to declare your m_vActiveB
member vector:
std::vector<ClassB> m_vActiveB;
and let the default constructor take care of destruction. You could alternatively use a vector of std::unique_ptr
, but, just like your current solution, you would not take advantage of the natural efficiency of contiguous elements in arrays.
In my system objects of ClassB themselves need to be constantly, added, removed and updated from the vector to keep a state model.
If your data model doesn't naturally base on the fact that elements are contiguous and have a particular order, an "unsorted", associative container like std::unordered_map
or std::unordered_set
might be a better solution here, especially if you insert and remove the elements that aren't residing at the end of the vector (and sit in the middle instead).
You might also want to take a look at std::list
and std::deque
which offer, respectively, fast insertion and deletion (which don't invalidate iterators) and fast insertion and deletion at both ends (which don't invalidate iterators either).
I recall that altering the vector contents invalidates all pointers.. is that correct?
If you call std::vector::erase
yes, all iterators will get invalidated (including the past the end iterator). Calling erase
will also make all references and pointers to the deleted object invalid (just like referencing any object that is destroyed/deallocated is invalid).
Upvotes: 4
Reputation: 15534
Using std::vector::erase
causes iterators at and past the point of erase to become invalidated. As such happens, the past-the-end iterator e
is no longer valid after the first iteration.
Instead compare directly against the iterator returned from std::vector::end
at every iteration. However, in your example there's no need to erase anything as m_vActiveB
is a member and will be destructed automatically after running the dtor (delete
ing the data pointed to is however required).
The iterator i
will also become invalidated when the erase happens as making a copy tmp
of the iterator i
and erasing it will still cause the same element to be erased from the vector (erasing a copy of an iterator does not help).
ClassA::~ClassA() {
for (vector<ClassB*>::iterator i = m_vActiveB.begin(); i != m_vActiveB.end(); ++i) {
//vector<ClassB*>::iterator tmp(i++); // Unnecessary
delete *i;
//m_vActiveB.erase(i); // Unnecessary as elements will be erased automatically.
}
}
Equivalent C++11 code:
ClassA::~ClassA() {
for (auto ptr : m_vActiveB) {
delete ptr;
}
}
A better alternative would be to use a C++11 smart pointer as e.g. std::unique_ptr
, that can handle the allocation and deallocation automatically.
class ClassA {
std::vector<std::unique_ptr<ClassB>> m_vActiveB; // No need for explicit destructor.
/* ... */
};
Or if you are not using polymorphism or have some other specific reason to use pointers, then why use pointers at all? Simply use a std:vector<ClassB>
.
Upvotes: 1
Reputation: 6155
From the docs: http://en.cppreference.com/w/cpp/container/vector/erase
Invalidates iterators and references at or after the point of the erase, including the end() iterator.
This means your i
and e
iterators have both been invalidated. So don't use erase
inside a loop like you're doing!
Also consider:
std::shared_ptr
instead of doing memory management yourself.m_vActiveB.clear()
when you've deleted them all, but you don't even need to do that because:m_vActiveB
is a member. It'll get tidied up for you, don't bother clearing it yourself.Upvotes: 2
Reputation: 16338
You are removing elements from the vector while iterating through it. That invalidates the iterators.
It should be something like this:
ClassA::~ClassA()
{
for (auto e : m_vActiveB)
{
delete e;
}
}
You don't have to clear the vector, it is destroyed anyway after the destructor has finished.
Upvotes: 0
Reputation: 141628
When you use vector::erase
, it invalidates any other iterator into the vector.
So you cannot use a "traditional" iterator loop.
The simplest way is as Rakibul Hasan suggests, just delete all pointers and then clear
the vector.
Upvotes: -1