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