stl container with std::unique_ptr’s vs boost::ptr_container

I decided to write up a short program that put a few polymorphic objects into a container (by pointer to heap), and then use that container with a std::algorithm. I chose std::remove_if just as an example.

Here is how I would do it with vector<unique_ptr<T>>:

#include <vector>
#include <memory>
#include <iostream>

class Animal
{
public:
    Animal() = default;
    Animal(const Animal&) = delete;
    Animal& operator=(const Animal&) = delete;
    virtual ~Animal() = default;

    virtual void speak() const = 0;
};

class Cat
    : public Animal
{
public:
    virtual void speak() const {std::cout << "Meow\n";}
    virtual ~Cat() {std::cout << "destruct Cat\n";}
};

class Dog
    : public Animal
{
public:
    virtual void speak() const {std::cout << "Bark\n";}
    virtual ~Dog() {std::cout << "destruct Dog\n";}
};

class Sheep
    : public Animal
{
public:
    virtual void speak() const {std::cout << "Baa\n";}
    virtual ~Sheep() {std::cout << "destruct Sheep\n";}
};

int main()
{
    typedef std::unique_ptr<Animal> Ptr;
    std::vector<Ptr> v;
    v.push_back(Ptr(new Cat));
    v.push_back(Ptr(new Sheep));
    v.push_back(Ptr(new Dog));
    v.push_back(Ptr(new Sheep));
    v.push_back(Ptr(new Cat));
    v.push_back(Ptr(new Dog));
    for (auto const& p : v)
        p->speak();
    std::cout << "Remove all sheep\n";
    v.erase(
        std::remove_if(v.begin(), v.end(),
                       [](Ptr& p)
                           {return dynamic_cast<Sheep*>(p.get());}),
        v.end());
    for (auto const& p : v)
        p->speak();
}

This outputs:

Meow
Baa
Bark
Baa
Meow
Bark
Remove all sheep
destruct Sheep
destruct Sheep
Meow
Bark
Meow
Bark
destruct Dog
destruct Cat
destruct Dog
destruct Cat

which looks good to me. However I found translating this to ptr_vector problematic:

boost::ptr_vector<Animal> v;
v.push_back(new Cat);
v.push_back(new Sheep);
v.push_back(new Dog);
v.push_back(new Sheep);
v.push_back(new Cat);
v.push_back(new Dog);
for (auto const& p : v)
    p.speak();
std::cout << "Remove all sheep\n";
v.erase(
    std::remove_if(v.begin(), v.end(),
                   [](Animal& p)
                       {return dynamic_cast<Sheep*>(&p);}),
    v.end());
for (auto const& p : v)
    p.speak();

algorithm:1897:26: error: overload resolution selected deleted operator '='
                *__first = _VSTD::move(*__i);
                ~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~
test.cpp:75:9: note: in instantiation of function template specialization 'std::__1::remove_if<boost::void_ptr_iterator<std::__1::__wrap_iter<void
      **>, Animal>, Sheep *(^)(Animal &)>' requested here
        std::remove_if(v.begin(), v.end(),
        ^
test.cpp:12:13: note: candidate function has been explicitly deleted
    Animal& operator=(const Animal&) = delete;
            ^
1 error generated.

The problem is a feature of boost::ptr_vector: the iterators don’t return the internally stored pointers. They return the pointers dereferenced. And thus when the container is used with std::algorithms, the algorithms try to copy the stored objects instead of the stored pointers to the objects.

If one accidentally forgets to make your polymorphic objects non-copyable, then copy semantics are automatically supplied, resulting in a run time error instead of a compile time error:

class Animal
{
public:
    Animal() = default;
    virtual ~Animal() = default;

    virtual void speak() const = 0;
};

Which now results in this erroneous output:

Meow
Baa
Bark
Baa
Meow
Bark
Remove all sheep
destruct Cat
destruct Dog
Meow
Baa
Bark
Baa
destruct Cat
destruct Sheep
destruct Dog
destruct Sheep

This run time error can not happen when using vector<unique_ptr>.

The impedance mismatch of storing containers of pointers but presenting containers of references appears at odds with safe use of the containers with generic algorithms. Indeed, this is why the ptr_containers come with custom versions of many of the algorithms. The correct way to do this job with ptr_containers is to use only those member algorithms:

v.erase_if([](Animal& p)
                 {return dynamic_cast<Sheep*>(&p);});

If you need a mutating sequence algorithm not supplied as a member of the ptr_containers, do not be tempted to reach for those in <algorithm>, or those generic algorithms supplied by other 3rd parties.

In summary, the boost::ptr_containers filled a real need when the only other practical option was std::vector<boost::shared_ptr<T>>. However now with std::vector<std::unique_ptr<T>>, the overhead argument is gone. And there appears to be both safety and flexibility advantages with the C++11 solution. If you need “clone semantics”, I would seriously consider writing your own clone_ptr<T> and using that with the std containers and algorithms.

Reuse of the std::lib will keep your options of containers more open than the boost lib (e.g. unordered_set/map, forward_list), and it will keep your options of std::algorithms open as wide as possible.

That being said, if you have working, debugged code already using the boost::ptr_containers, there’s no urgent need to change it.

Leave a Comment