Search code examples
c++stlauto-ptr

Can I get away with putting auto_ptr in a STL container?


I am inheriting an interface, and implementing a virtual function that is supposed to do some work on a list of dynamically allocated objects. The first step is to remove duplicates from the list based on some custom equivalence criteria:

class Foo { /* ... */ };

struct FooLess
{
    bool operator()(const Foo *lhs, const Foo *rhs);
}

struct FooEqual
{
    bool operator()(const Foo *lhs, const Foo *rhs);
}

void doStuff(std::list<Foo*> &foos)
{
    // use the sort + unique idiom to find and erase duplicates
    FooLess less;
    FooEqual equal;
    foos.sort( foos.begin(), foos.end(), less );
    foos.erase( 
        std::unique( foos.begin(), foos.end(), equal ), 
        foos.end() ); // memory leak!
}

The problem is that using sort + unique doesn't clean up the memory, and the elements to be erased have unspecified values after unique, so I cannot perform the cleanup myself before eraseing. I was considering something like this:

void doStuff(std::list<Foo*> &foos)
{
    // make a temporary copy of the input as a list of auto_ptr's
    std::list<auto_ptr<Foo>> auto_foos;
    for (std::list<Foo>::iterator it = foos.begin(); it != foos.end(); ++it) 
        auto_foos.push_back(auto_ptr<Foo>(*it));
    foos.clear();

    FooLess less; // would need to change implementation to work on auto_ptr<Foo>
    FooEqual equal; // likewise
    auto_foos.sort( auto_foos.begin(), auto_foos.end(), less );
    auto_foos.erase( 
        std::unique( auto_foos.begin(), auto_foos.end(), equal ), 
        auto_foos.end() ); // okay now, duplicates deallocated

    // transfer ownership of the remaining objects back
    for (std::list<auto_ptr<Foo>>::iterator it = auto_foos.begin(); 
        it != auto_foos.end(); ++it) 
    { foos.push_back(it->get()); it->release(); }
}

Will this be okay, or am I missing something?

I am not able to use C++11 (Boost might be possible) or change the function signature to accept a list of straightforward Foos.


Solution

  • There are generally the following methods you can use in C++98:

    1. Define some pointer that will do what std::auto_ptr can't do. There was an old version of that thing, which contained an additional field of type bool that marked ownership. It was marked mutable, so it could be modified also in the object being read from when copying. The object was deleted at the end only if owned was true. Something like:

    ==

    template <class T> class owning_ptr
    {
       T* ptr;
       mutable bool owns;
       public:
       void operator =(T* src) { ptr = src; owns = true; }
       owning_ptr(const owning_ptr& other)
       {
           // copy the pointer, but STEAL ownership!
           ptr = other.ptr; owns = other.owns; other.owns = false;
       }
       T* release() { owns = false; return ptr; }
       ~owning_ptr() { if ( owns ) delete ptr; }
       /* ... some lacking stuff ..*/
     };
    
    1. You may try out boost::shared_ptr

    2. Instead of std::unique, you may try to do std::adjacent_find in a loop. Then you'll just find all elements that are "the same" as by your equal. If there's more than one element, you will erase them in place (you are allowed to do it because it's a list, so iterators remain valid).