unique_ptr smart shared_ptr make_unique how example c++ stl c++11 unique-ptr boost-ptr-container

c++ - smart - unique_ptr example



contenedor stl con std:: unique_ptr''s vs boost:: ptr_container (2)

Con c ++ 11, me preguntaba si hay un reemplazo de boost :: ptr_containers en c ++ 11. Sé que puedo usar, por ejemplo, un std::vector<std::unique_ptr<T> > , pero no estoy seguro si este es un reemplazo completo. ¿Cuál es la forma recomendada de manejar estos casos?


Decidí escribir un programa corto que pusiera algunos objetos polimórficos en un contenedor (de puntero a montón) y luego usar ese contenedor con un algoritmo std ::. Elegí std::remove_if solo como ejemplo.

Así es como lo haría con 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(); }

Esto produce:

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

que me queda bien. Sin embargo, encontré que traducir esto a ptr_vector problemático:

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.

El problema es una característica de boost::ptr_vector : los iteradores no devuelven los punteros almacenados internamente. Devuelven los punteros desreferenciados. Y así, cuando el contenedor se usa con std::algorithms , los algoritmos intentan copiar los objetos almacenados en lugar de los punteros almacenados a los objetos.

Si uno olvida accidentalmente hacer que sus objetos polimórficos no puedan copiarse, entonces la semántica de la copia se suministra automáticamente, lo que resulta en un error de tiempo de ejecución en lugar de un error de tiempo de compilación:

class Animal { public: Animal() = default; virtual ~Animal() = default; virtual void speak() const = 0; };

Lo que ahora resulta en esta salida errónea:

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

Este error de tiempo de ejecución no puede ocurrir cuando se usa vector<unique_ptr> .

El desajuste de impedancia de almacenar contenedores de punteros pero presentar contenedores de referencias aparece en desacuerdo con el uso seguro de los contenedores con algoritmos genéricos. De hecho, esta es la razón por la que los ptr_containers vienen con versiones personalizadas de muchos de los algoritmos. La forma correcta de hacer este trabajo con ptr_containers es usar solo los algoritmos de miembros:

v.erase_if([](Animal& p) {return dynamic_cast<Sheep*>(&p);});

Si necesita un algoritmo de secuencia de mutación no suministrado como miembro de los ptr_containers, no tenga la tentación de llegar a los que están en <algorithm> , ni a los algoritmos genéricos suministrados por terceros.

En resumen, boost :: ptr_containers satisfacía una necesidad real cuando la única otra opción práctica era std::vector<boost::shared_ptr<T>> . Sin embargo, ahora con std::vector<std::unique_ptr<T>> , el argumento de la sobrecarga se ha ido. Y parece que hay ventajas de seguridad y flexibilidad con la solución C ++ 11. Si necesita "semántica de clones", consideraría seriamente escribir su propio clone_ptr<T> y usarlo con los contenedores y algoritmos clone_ptr<T> .

La reutilización de std :: lib mantendrá sus opciones de contenedores más abiertas que el boost lib (por ejemplo, unordered_set / map, forward_list), y mantendrá las opciones de std :: algorithms lo más abiertas posible.

Dicho esto, si tiene un código de trabajo y depurado que ya usa boost :: ptr_containers, no hay necesidad urgente de cambiarlo.


Realmente solucionan dos problemas similares pero diferentes.

Un contenedor de punteros es una forma de almacenar objetos en un contenedor que resulta ser punteros a la memoria asignada en lugar de valores. Hacen todo lo posible para ocultar el hecho de que son un contenedor de indicadores. Esto significa:

  • Las entradas en el contenedor no pueden ser NULL.
  • Los valores que obtiene de los iteradores y las funciones son referencias al tipo, no punteros al tipo.
  • Trabajar con muchos algoritmos estándar puede ser ... complicado. Y por "complicado", quiero decir roto. Los contenedores de punteros tienen sus propios algoritmos incorporados.

Sin embargo, el hecho de que los contenedores de punteros sepan que son contenedores de punteros puede ofrecer algunas funciones nuevas:

  • Una función de miembro clone que realiza una copia profunda, mediante el uso de un determinado concepto "Cloneable" en el tipo del objeto.
  • La capacidad de un contenedor para liberar la propiedad de sus objetos (después de una copia superficial, por ejemplo).
  • Funciones incorporadas para transferir la propiedad a otros contenedores.

Realmente son conceptos muy diferentes. Hay muchas cosas que tendría que hacer manualmente que los contenedores de punteros pueden hacer automáticamente con funciones especializadas.

Si realmente necesita un contenedor de punteros , puede usar contenedores de unique_ptr . Pero si necesita almacenar un montón de objetos que acumulará en el montón y desea jugar juegos especiales con ellos relacionados con la propiedad, los contenedores de punteros no son una mala idea.