STL 容器与 std::unique_ptr vs boost::p tr_container

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

本文关键字:boost vs container tr ptr unique std STL      更新时间:2023-10-16

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

我决定编写一个简短的程序,将一些多态对象放入一个容器中(通过指向堆的指针),然后将该容器与std::算法一起使用。我选择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 << "Meown";}
    virtual ~Cat() {std::cout << "destruct Catn";}
};
class Dog
    : public Animal
{
public:
    virtual void speak() const {std::cout << "Barkn";}
    virtual ~Dog() {std::cout << "destruct Dogn";}
};
class Sheep
    : public Animal
{
public:
    virtual void speak() const {std::cout << "Baan";}
    virtual ~Sheep() {std::cout << "destruct Sheepn";}
};
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 sheepn";
    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 sheepn";
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_container带有许多算法的自定义版本的原因。使用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::算法的选项尽可能广泛地开放。

也就是说,如果您已经使用boost::ptr_containers对代码进行了工作和调试,那么就没有迫切需要更改它

它们确实解决了两个相似但不同的问题。

指针容器是一种将对象存储在容器中的方法,这些对象恰好是指向已分配内存而非值的指针。他们尽其所能隐藏他们是指针容器的事实。这意味着:

  • 容器中的条目不能为NULL
  • 从迭代器和函数中获得的值是对类型的引用,而不是对类型的指针
  • 使用许多标准算法可能…很棘手。我所说的"棘手"是指崩溃。指针容器有自己的内置算法

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

  • clone成员函数,通过对对象类型使用特定的"可克隆"概念来执行深度复制
  • 容器释放其对象所有权的能力(例如,在浅层复制之后)
  • 用于将所有权转移到其他容器的内置功能

它们确实是完全不同的概念。指针容器可以使用专门的函数自动完成许多手动操作。

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