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

谁都会走 提交于 2019-11-26 16:50:12

问题


With c++11 out there, I was asking myself if there is a replacement of the boost::ptr_containers in c++11. I know I can use e.g. a std::vector<std::unique_ptr<T> >, but I'm not sure if this is a complete replacement. What is the recommended way of handling these cases?


回答1:


They really solve two similar but different problems.

A pointer container is a way to store objects in a container that just so happen to be pointers to allocated memory rather than values. They do everything in their power to hide the fact that they are a container of pointers. This means:

  • Entries in the container cannot be NULL.
  • Values you get from iterators and functions are references to the type, not pointers to the type.
  • Working with many standard algorithms can be... tricky. And by "tricky", I mean broken. Pointer containers have their own built-in algorithms.

However, the fact that pointer containers know that they're containers of pointers, they can offer some new functionality:

  • A clone member function that performs a deep copy, via the use of a certain "Cloneable" concept on the type of the object.
  • The ability of a container to release ownership of its objects (after a shallow copy, for example).
  • Built-in functions to transfer ownership to other containers.

They really are quite different concepts. There is a lot of stuff you would have to do manually that pointer containers can do automatically with specialized functions.

If you really need a container of pointers, then you can use containers of unique_ptr. But if you need to store a bunch of objects that you happen to heap allocate, and you want to play special games with them involving ownership and such, then the pointer containers are not a bad idea.




回答2:


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.



来源:https://stackoverflow.com/questions/9469968/stl-container-with-stdunique-ptrs-vs-boostptr-container

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!