Я решил написать короткую программу, которая помещает несколько полиморфных объектов в контейнер (по указателю в кучу), а затем использую этот контейнер с помощью алгоритма 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 << "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>
, или к тем общим алгоритмам, предоставленным другими третьими сторонами.
Таким образом, boost :: ptr_containers полностью удовлетворял потребности, когда единственным другим практическим вариантом был 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 :: алгоритмов открытыми как можно шире.
Тем не менее, если у вас есть работающий отлаженный код, уже использующий boost :: ptr_containers, его не нужно срочно менять.