Vortico
Vortico

Reputation: 2749

C++ container which automatically deletes elements when destructed

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

Answers (3)

Lorenz Zhao
Lorenz Zhao

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

n. m. could be an AI
n. m. could be an AI

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

Antimony
Antimony

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

Related Questions