2012-02-27 8 views
15

Con C++ 11 por ahí, me preguntaba si hay un reemplazo de boost :: ptr_containers en C++ 11. Sé que puedo usar, p. a std::vector<std::unique_ptr<T> >, pero no estoy seguro si esto es un reemplazo completo. ¿Cuál es la forma recomendada de manejar estos casos?contenedor stl con std :: unique_ptr's boost :: ptr_container

+2

Con el 'unique_ptr' todavía tiene que eliminar la referencia de los nodos, pero aparte de eso, deberían comportarse de la misma manera. –

Respuesta

19

Realmente resuelven dos problemas similares pero diferentes.

Un contenedor de puntero es una forma de almacenar objetos en un contenedor que, simplemente, son punteros a la memoria asignada en lugar de valores. Hacen todo lo que está en su poder para ocultar ocultar el hecho de que son un contenedor de punteros. Esto significa:

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

Sin embargo, el hecho de que los contenedores puntero saben que son contenedores de punteros, que pueden ofrecer algunas nuevas funcionalidades:

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

Realmente son conceptos bastante diferentes. Hay muchas cosas que debería hacer manualmente que los contenedores de puntero pueden hacer automáticamente con funciones especializadas.

Si realmente necesita un contenedor de punteros, entonces puede utilizar contenedores de unique_ptr. Pero si necesita almacenar un conjunto de objetos que asigna de manera aleatoria, y desea jugar juegos especiales con ellos que impliquen propiedad y cosas así, entonces los contenedores de puntero no son una mala idea.

+1

Supongo que se podría decir que uno de ellos es explícitamente un contenedor de punteros, mientras que el otro es un contenedor destinado a objetos polimórficos ... – Mehrdad

22

Decidí escribir un programa corto que colocaba unos pocos objetos polimórficos en un contenedor (por puntero a montón) y luego usaba ese contenedor con un algoritmo std ::. Elegí std::remove_if solo como un 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(); 
} 

Este salidas:

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 

la que se ve bien para mí. Sin embargo, me encontré a traducir esta problemática 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. 

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 en los objetos.

Si uno accidentalmente se olvida de hacer sus objetos polimórficos no copiable, y luego copiar la semántica se suministran de forma automática, 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; 
}; 

Que ahora se traduce en esta errónea salida:

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 al utilizar vector<unique_ptr>.

La impedancia no coincidente de almacenar contenedores de punteros pero presentar contenedores de referencias parece estar en desacuerdo con el uso seguro de los contenedores con algoritmos genéricos. De hecho, esta es la razón por la cual los ptr_containers vienen con versiones personalizadas de muchos de los algoritmos. La forma correcta de hacer este trabajo con ptr_containers es utilizar única esos algoritmos miembros:

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

Si necesita un algoritmo de secuencia de la mutación no suministrado como miembro de los ptr_containers, no tener la tentación de llegar para aquellos en <algorithm>, o aquellos algoritmos genéricos suministrados por terceros.

En resumen, el boost :: ptr_containers suplí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 general se ha ido. Y parece que existen ventajas de seguridad y flexibilidad con la solución C++ 11. Si necesita "semántica de clonación", consideraría seriamente escribir su propio clone_ptr<T> y usarlo con los contenedores estándar y los algoritmos.

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

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

+0

"el argumento general se ha ido" - Hoy, hice algunas pruebas con VS2013-Express y sorprendentemente obtengo mejores resultados de rendimiento con 'ptr_vector' que con' vector '(/ O2 release build; boost 1.55). Este comentario viene con un YMMV claro, aún no me he metido en esto, pero si uno está en un espacio muy reducido en cuanto a rendimiento, podría ser interesante mirar ambos. –

+0

Interesante, gracias por el informe. No tengo VS2013 para experimentar. Es 'sizeof (unique_ptr ) == sizeof (T *)'? ¿Es/O2 la configuración de optimización más alta? –

+0

Sí, sizeof (uq_ptr) == sizeof (T *) == 4./O2 es "maximizar para la velocidad" en VS. Veo una ganancia de velocidad del 50% con ptr_vector en el acceso de índice '[]'. –

Cuestiones relacionadas