19

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

4

2 回答 2

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>或其他第 3 方提供的通用算法。

总之,当唯一的其他实用选项是std::vector<boost::shared_ptr<T>>. 但是现在有了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 的工作、调试代码,则没有迫切需要更改它。

于 2012-02-27T21:14:53.153 回答
20

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

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

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

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

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

它们确实是完全不同的概念。您必须手动完成很多事情,而指针容器可以使用专门的功能自动完成。

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

于 2012-02-27T18:28:59.293 回答