Reputation: 2749
I would like to create a list or map of references in C++ which automatically deletes its elements upon their destruction. Here is a structure demonstrating the idea.
class Foo;
class Bar
{
Foo foo;
};
void main(void)
{
std::vector<Foo> foos;
{
Bar bar;
foos.push_back(bar.foo);
// foos[0] should be the same reference as bar.foo
}
// bar is now destructed
// foos.size() should be 0
}
There are two things wrong with my code. When push_back()
is called with a std::vector, it creates a copy of the Foo object. This would be unacceptable since the elements of foos
should reflect any changes done upon bar.foo
. Secondly, even if the foos
vector could store references, the Foo object would not be removed from the list after leaving the scope where bar
was created. Ideally, I would like the Foo reference to be removed from foos
upon its destruction.
Before I implement my own solution, I'd love to know of any third party libraries (Boost, possibly?) which offer this, or any proper techniques or strategies I should be using for this. Thanks!
Upvotes: 1
Views: 1409
Reputation: 377
Change your list to hold references by using weak_ptr (as suggested by Antimony) and automate the removal of destructed elements as follows.
First of all let's clarify some wording: your title should be "C++ container which automatically removes references to elements when destructed", because deleting an element actually destructs it, so you don't want it to be "deleted" upon destruction, but rather want the reference to it to be removed from the container.
std::list<std::weak_ptr<Foo>> foos;
{
// create a new entry in the container
const auto& iter = foos.insert(foos.end(), std::weak_ptr<Foo>());
// create an object of type "Foo" and store it in a shared_ptr
std::shared_ptr<Foo> foo
(new Foo
// set a custom deleter that actually deletes the object
// AND erases it from the container
, [&foos, iter](Foo* ptr)
{
delete ptr;
foos.erase(iter);
}
);
// set the pointer to the element into the container
*iter = foo;
}
// the shared_ptr "foo" ran out of scope;
// as there is no further strong reference to its object
// , the custom deleter was called which has removed the entry from the container
// so foos.size() is now 0
Note that I changed the container from vector to list, because a vector might invalidate the iterator (that is passed to the custom deleter, see Does resizing a vector invalidate iterators?) while a list does not. If you want to stick to a vector you could use the index as reference to the element as passed to the custom deleter, but be aware that inserting elements to the vector before end() would then corrupt this reference.
Upvotes: 0
Reputation: 119877
If you need more than one reference to the same object, you probably need a container of "smart" pointers like std::shared_ptr
(or one from Boost if you don't have C++11).
Upvotes: 1
Reputation: 39451
I think the best option is to use a container of weak pointers. (Available in Boost if your compiler doesn't support C++11 or TR1 for some reason.)
A weak pointer will automatically invalidate itself when the target is destructed, assuming you initialized things properly.
Here's some sample code for getting all valid pointers in a container of weak pointers.
template <class T>
std::vector<std::shared_ptr<T> > MassLock(const std::vector<std::weak_ptr<T> >& weakptrs)
{
std::vector<std::shared_ptr<T> > sharedptrs;
for(unsigned int i=0; i<weakptrs.size(); ++i)
{
if (std::shared_ptr<T> temp = weakptrs.at(i).lock())
{
sharedptrs.push_back(temp);
}
}
return sharedptrs;
}
And here's how to clean out all invalid pointers.
template <class T>
struct isInvalid : public std::unary_function<T, bool>
{
bool operator() (T rhs) { return !rhs.lock(); }
};
template <class T>
void EraseInvalid( std::vector<T>& targetlist )
{
targetlist.erase( remove_if(targetlist.begin(), targetlist.end(), isInvalid<T>() ), targetlist.end());
}
Upvotes: 5