С С++ 11, я спрашивал себя, есть ли замена boost:: ptr_containers в С++ 11. Я знаю, что могу использовать, например. a std::vector<std::unique_ptr<T> >
, но я не уверен, что это полная замена. Каков рекомендуемый способ обработки этих случаев?
Ответ 1
Они действительно решают две похожие, но разные проблемы.
Контейнер-указатель - это способ хранения объектов в контейнере, которые просто являются указателями на выделенную память, а не на значения. Они делают все возможное, чтобы скрыть тот факт, что они являются контейнером указателей. Это означает:
- Записи в контейнере не могут быть NULL.
- Значения, которые вы получаете от итераторов и функций, являются ссылками на тип, а не указателями на тип.
- Работа со многими стандартными алгоритмами может быть... сложной. И "сложно", я имею в виду сломанный. Контейнеры-указатели имеют свои собственные встроенные алгоритмы.
Однако тот факт, что контейнеры указателей знают, что они являются контейнерами указателей, могут предложить некоторые новые функции:
- A
clone
функция-член, которая выполняет глубокую копию, используя определенную концепцию "Cloneable" для типа объекта.
- Способность контейнера освобождать право собственности на свои объекты (например, после мелкой копии).
- Встроенные функции для передачи права собственности на другие контейнеры.
Они действительно совсем другие понятия. Существует много вещей, которые вам придется делать вручную, чтобы контейнеры-указатели могли автоматически выполнять специализированные функции.
Если вам действительно нужен контейнер указателей, вы можете использовать контейнеры unique_ptr
. Но если вам нужно сохранить кучу объектов, которые вы выбрали для выделения кучи, и вы хотите играть с ними специальные игры с участием владельца и т.д., То контейнеры-указатели - неплохая идея.
Ответ 2
Я решил написать короткую программу, которая помещает несколько полиморфных объектов в контейнер (указателем на кучу), а затем использовать этот контейнер с помощью 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>
, или тех общих алгоритмов, которые предоставляются другими третьими сторонами.
В итоге, boost:: ptr_containers заполнили реальную потребность, когда единственным другим практическим вариантом было std::vector<boost::shared_ptr<T>>
. Однако теперь с std::vector<std::unique_ptr<T>>
аргумент служебной информации исчез. И, по-видимому, с решением С++ 11 существуют преимущества безопасности и гибкости. Если вам нужна "семантика клонов", я бы серьезно подумал о написании собственного clone_ptr<T>
и использовании этого с помощью std-контейнеров и алгоритмов.
Повторное использование std:: lib будет держать ваши варианты контейнеров более открытыми, чем boost lib (например, unordered_set/map, forward_list), и это будет поддерживать как можно более широкие возможности std:: алгоритмов.
При этом, если у вас есть работа, отлаженный код уже использует boost:: ptr_containers, нет необходимости срочно его изменять.