stl容器与std :: unique_ptr的vs boost :: ptr_container

时间:2012-02-27 18:11:04

标签: c++ stl c++11 unique-ptr boost-ptr-container

有了c ++ 11,我问自己是否在c ++ 11中替换了boost :: ptr_containers。我知道我可以使用例如一个std::vector<std::unique_ptr<T> >,但我不确定这是否完全替代。处理这些案件的推荐方法是什么?

2 个答案:

答案 0 :(得分:22)

我决定写一个简短的程序,将一些多态对象放入容器(通过指向堆的指针),然后将该容器与std :: algorithm一起使用。我选择std::remove_if作为例子。

以下是我如何使用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();
}

输出:

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

对我来说很好看。但是我发现将其转换为ptr_vector有问题:

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.

问题是boost::ptr_vector的一个特性:迭代器不返回内部存储的指针。他们返回被解除引用的指针。因此,当容器与std::algorithms一起使用时,算法会尝试复制存储的对象而不是存储的指向对象的指针。

如果有人意外忘记使您的多态对象不可复制,则会自动提供复制语义,从而导致运行时错误而不是编译时错误:

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

    virtual void speak() const = 0;
};

现在导致错误的输出:

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

使用vector<unique_ptr>时,不会发生此运行时错误。

存储指针容器但呈现参考容器的阻抗不匹配似乎与使用通用算法安全使用容器不一致。实际上,这就是为什么ptr_containers带有许多算法的自定义版本。使用ptr_containers完成这项工作的正确方法是使用 那些成员算法:

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

如果您需要一个不作为ptr_containers成员提供的变异序列算法,请不要试图找到<algorithm>中的那些,或其他第三方提供的那些通用算法。

总之,当唯一的其他实用选项是std::vector<boost::shared_ptr<T>>时,boost :: ptr_containers填补了真正的需求。但是现在使用std::vector<std::unique_ptr<T>>,开销参数消失了。 C ++ 11解决方案似乎具有安全性和灵活性优势。如果您需要“克隆语义”,我会认真考虑编写您自己的clone_ptr<T>并将其与std容器和算法一起使用。

重用std :: lib将使你的容器选项比boost lib更开放(例如unordered_set / map,forward_list),它将使你的std :: algorithms选项尽可能地开放。

话虽如此,如果您已经使用boost :: ptr_containers工作,调试过的代码,则不需要更改它。

答案 1 :(得分:20)

他们真的解决了两个相似但不同的问题。

指针容器是一种在容器中存储对象的方法,它恰好是指向已分配内存而不是值的指针。他们竭尽全力隐藏这是一个指针容器的事实。这意味着:

  • 容器中的条目不能为NULL。
  • 从迭代器和函数获得的值是引用到类型,而不是指向类型的指针。
  • 使用许多标准算法可能会很棘手。而“狡猾”,我的意思是破碎。指针容器有自己的内置算法。

然而,指针容器知道它们是指针的容器这一事实,它们可以提供一些新的功能:

  • clone成员函数,通过对对象类型使用某个“可克隆”概念来执行深层复制。
  • 容器释放其对象所有权的能力(例如,在浅拷贝之后)。
  • 将所有权转移到其他容器的内置功能。<​​/ li>

他们真的是完全不同的概念。你需要手动完成很多东西,指针容器可以通过专门的函数自动完成。

如果您确实需要指针的容器,则可以使用unique_ptr的容器。但是如果你需要存储一堆你碰巧堆分配的对象,并且想要使用涉及所有权等的特殊游戏,那么指针容器并不是一个坏主意。