繁体   English   中英

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

[英]stl container with std::unique_ptr's vs boost::ptr_container

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

我决定编写一个简短的程序,将一些多态对象放入容器(通过指向堆的指针),然后将该容器与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工作,调试过的代码,就没有迫切需要改变它。

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

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

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

但是,指针容器知道它们是指针的容器,它们可以提供一些新功能:

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

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

如果你真的需要一个指针容器,那么你可以使用unique_ptr容器。 但是如果你需要存储一堆碰巧堆积的对象,并且想要与它们一起玩特殊游戏,那么指针容器并不是一个坏主意。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM