¿Debo almacenar objetos completos o punteros a objetos en contenedores?

162

Diseñando un nuevo sistema desde cero. Usaré el STL para almacenar listas y mapas de ciertos objetos de larga vida.

Pregunta: ¿Debo asegurarme de que mis objetos tengan constructores de copias y almacenar copias de objetos dentro de mis contenedores STL, o es generalmente mejor administrar la vida y el alcance yo mismo y simplemente almacenar los punteros a esos objetos en mis contenedores STL?

Me doy cuenta de que esto es algo breve en detalles, pero estoy buscando la mejor respuesta "teórica" ​​si existe, ya que sé que ambas soluciones son posibles.

Dos desventajas muy obvias para jugar con punteros: 1) Yo mismo debo administrar la asignación / desasignación de estos objetos en un ámbito más allá del STL. 2) No puedo crear un objeto temporal en la pila y agregarlo a mis contenedores.

¿Me falta algo más?

Stéphane
fuente
36
Dios, amo este sitio, esta es la pregunta EXACTA en la que estaba pensando hoy ... gracias por hacer el trabajo de preguntar por mí :-)
eviljack
2
Otra cosa interesante es que debemos verificar si el puntero se agregó realmente a la colección y si no es así, probablemente deberíamos llamar a Delete para evitar pérdidas de memoria ... if ((set.insert (pointer)). second = false) {Eliminar puntero;}
javapowered

Respuestas:

68

Dado que las personas están hablando de la eficiencia del uso de punteros.

Si está considerando usar un std :: vector y si las actualizaciones son pocas y a menudo itera sobre su colección y es un objeto de almacenamiento no polimórfico, las "copias" serán más eficientes ya que obtendrá una mejor localidad de referencia.

Otoh, si las actualizaciones son comunes, el almacenamiento de punteros ahorrará los costos de copia / reubicación.

Torbjörn Gyllebring
fuente
77
En términos de localidad de caché, el almacenamiento de punteros en vectores puede ser eficiente si se usa junto con un asignador personalizado para los pointees. El asignador personalizado tiene que cuidar la localidad de la memoria caché, por ejemplo, utilizando la ubicación nueva (ver en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
amit
47

Esto realmente depende de tu situación.

Si sus objetos son pequeños y hacer una copia del objeto es liviano, entonces almacenar los datos dentro de un contenedor stl es sencillo y fácil de administrar en mi opinión porque no tiene que preocuparse por la administración de por vida.

Si sus objetos son grandes, y tener un constructor predeterminado no tiene sentido, o las copias de los objetos son costosas, entonces el almacenamiento con punteros es probablemente el camino a seguir.

Si decide utilizar punteros a los objetos, eche un vistazo a la biblioteca de contenedores de punteros de impulso . Esta biblioteca de impulso envuelve todos los contenedores STL para su uso con objetos asignados dinámicamente.

Cada contenedor de puntero (por ejemplo, ptr_vector) toma posesión de un objeto cuando se agrega al contenedor y administra la vida útil de esos objetos por usted. También tiene acceso a todos los elementos en un contenedor ptr_ por referencia. Esto te permite hacer cosas como

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Estas clases envuelven los contenedores STL y funcionan con todos los algoritmos STL, lo cual es realmente útil.

También hay facilidades para transferir la propiedad de un puntero en el contenedor a la persona que llama (mediante la función de liberación en la mayoría de los contenedores).

Nick Haddad
fuente
38

Si está almacenando objetos polimórficos, siempre debe usar una colección de punteros de clase base.

Es decir, si planea almacenar diferentes tipos derivados en su colección, debe almacenar punteros o ser devorado por el demonio de corte.

Torbjörn Gyllebring
fuente
1
¡Me encantó el demonio rebanador!
idichekop
22

Lamento saltar en 3 años después del evento, pero una nota de advertencia aquí ...

En mi último gran proyecto, mi estructura de datos central era un conjunto de objetos bastante sencillos. Aproximadamente un año después del proyecto, a medida que evolucionaban los requisitos, me di cuenta de que el objeto realmente tenía que ser polimórfico. Tomó algunas semanas de cirugía cerebral difícil y desagradable arreglar la estructura de datos para que fuera un conjunto de punteros de clase base y manejar todo el daño colateral en el almacenamiento de objetos, el lanzamiento, etc. Me tomó un par de meses convencerme de que el nuevo código estaba funcionando. Por cierto, esto me hizo pensar mucho sobre cuán bien diseñado está el modelo de objetos de C ++.

En mi gran proyecto actual, mi estructura de datos central es un conjunto de objetos bastante sencillos. Aproximadamente un año después del proyecto (que es hoy), me di cuenta de que el objeto realmente necesita ser polimórfico. De vuelta a la red, encontré este hilo y encontré el enlace de Nick a la biblioteca del contenedor de punteros Boost. Esto es exactamente lo que tuve que escribir la última vez para arreglar todo, así que lo intentaré esta vez.

La moraleja, para mí, de todos modos: si su especificación no está 100% moldeada en piedra, busque consejos, y puede ahorrarse mucho trabajo más adelante.

EML
fuente
Las especificaciones nunca están escritas en piedra. No creo que eso signifique que debas usar contenedores de puntero exclusivamente, aunque los contenedores de puntero Boost parecen hacer que esa opción sea mucho más atractiva. Soy escéptico de que necesite revisar todo su programa de una vez si decide que un contenedor de objetos debe convertirse en un contenedor de puntero. Este podría ser el caso en algunos diseños. En ese caso, es un diseño frágil. En ese caso, no culpe su problema a la "debilidad" de los contenedores de objetos.
allyourcode
Podrías haber dejado el elemento en el vector con semántica de valor y haber realizado el comportamiento polimórfico en el interior.
Billy ONeal
19

¿Por qué no obtener lo mejor de ambos mundos? Haga un contenedor de punteros inteligentes (como boost::shared_ptro std::shared_ptr). No tiene que administrar la memoria, y no tiene que lidiar con operaciones de copia grandes.

Branan
fuente
¿En qué se diferencia este enfoque de lo que Nick Haddad sugirió al usar la biblioteca de contenedores de puntero Boost?
Thorsten Schöning
10
@ ThorstenSchöning std :: shared_ptr no agrega una dependencia en boost.
James Johnston el
No puede usar punteros compartidos para manejar su polimorfismo, por lo que eventualmente perderá esta característica con este enfoque, a menos que explícitamente
eche
11

Generalmente, almacenar los objetos directamente en el contenedor STL es mejor, ya que es más simple, más eficiente y más fácil de usar.

Si su propio objeto tiene una sintaxis no copiable o es un tipo base abstracto, necesitará almacenar punteros (lo más fácil es usar shared_ptr)

Greg Rogers
fuente
44
No es más eficiente si sus objetos son grandes y mueve elementos con frecuencia.
allyourcode
3

Parece que tienes una buena comprensión de la diferencia. Si los objetos son pequeños y fáciles de copiar, almacénelos.

Si no, pensaría en almacenar punteros inteligentes (no auto_ptr, un puntero inteligente de recuento de referencias) a los que asigne en el montón. Obviamente, si opta por punteros inteligentes, entonces no puede almacenar objetos asignados a la pila temporal (como ha dicho).

@ Torbjörn hace un buen punto sobre el corte.

Lou Franco
fuente
1
Ah, y no siempre jamás crear una colección de auto_ptr de
Torbjörn Gyllebring
Correcto, auto_ptr no es un puntero inteligente, no cuenta el ref.
Lou Franco
auto_ptr tampoco tiene una semántica de copia no destructiva. El acto de asignación de un auto_ptr de onea anotherliberará la referencia de oney cambiará one.
Andy Finkenstadt
2

Si se hace referencia a los objetos en otra parte del código, almacénelos en un vector de boost :: shared_ptr. Esto garantiza que los punteros al objeto seguirán siendo válidos si cambia el tamaño del vector.

Es decir:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Si nadie más almacena punteros a los objetos, o la lista no crece y se contrae, simplemente almacene como objetos antiguos:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

fuente
1

Esta pregunta me ha estado molestando por un tiempo.

Me inclino por almacenar punteros, pero tengo algunos requisitos adicionales (SWIG lua wrappers) que podrían no aplicarse a usted.

El punto más importante en esta publicación es probarlo usted mismo , usando sus objetos

Lo hice hoy para probar la velocidad de llamar a una función miembro en una colección de 10 millones de objetos, 500 veces.

La función actualiza x e y en función de xdir e ydir (todas las variables miembro flotante).

Utilicé una std :: list para contener ambos tipos de objetos, y descubrí que almacenar el objeto en la lista es un poco más rápido que usar un puntero. Por otro lado, el rendimiento fue muy cercano, por lo que se trata de cómo se utilizarán en su aplicación.

Como referencia, con -O3 en mi hardware, los punteros tardaron 41 segundos en completarse y los objetos sin procesar tardaron 30 segundos en completarse.

Meleneth
fuente