std :: shared_ptr como último recurso?

59

Estaba viendo las transmisiones de "Going Native 2012" y me di cuenta de la discusión std::shared_ptr. Me sorprendió un poco escuchar la opinión algo negativa de Bjarne std::shared_ptry su comentario de que debería usarse como "último recurso" cuando el tiempo de vida de un objeto es incierto (lo cual creo que, según él, no debería ser el caso).

¿Alguien querría explicar esto con un poco más de profundidad? ¿Cómo podemos programar sin std::shared_ptradministrar los tiempos de vida de los objetos de una manera segura ?

ronag
fuente
8
¿No usas punteros? ¿Tener un propietario distinto del objeto, que maneja la vida?
Bo Persson
2
¿Qué pasa con los datos explícitamente compartidos? Es difícil no usar punteros. También std :: shared_pointer haría el sucio "gestionar la vida útil" en ese caso
Kamil Klimek
66
¿Has considerado escuchar menos el consejo presentado y más el argumento detrás de ese consejo? Explica bastante bien el tipo de sistema en el que funcionaría este tipo de consejo.
Nicol Bolas
@NicolBolas: escuché el consejo y el argumento, pero obviamente no sentí que lo entendía lo suficientemente bien.
Ronag
¿A qué hora dice "último recurso"? Mirando el bit a los 36 minutos en ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/… ) dice que desconfía del uso de punteros, pero se refiere a punteros en general, no solo shared_ptr y unique_ptr sino incluso ' puntero regular. Él implica que los objetos mismos (y no los punteros a los objetos asignados con nuevos) deben ser preferidos. ¿Fue lo que estaba pensando más tarde en la presentación?
Pharap

Respuestas:

55

Si puede evitar la propiedad compartida, su aplicación será más simple y fácil de entender y, por lo tanto, menos susceptible a los errores introducidos durante el mantenimiento. Los modelos de propiedad complejos o poco claros tienden a generar acoplamientos difíciles de seguir de diferentes partes de la aplicación a través de un estado compartido que puede no ser fácilmente rastreable.

Dado esto, es preferible usar objetos con duración de almacenamiento automático y tener subobjetos de "valor". De lo contrario, unique_ptrpuede ser una buena alternativa para shared_ptrestar, si no un último recurso, en la lista de herramientas deseables.

CB Bailey
fuente
55
+1 por señalar que el problema no es el techno en sí (propiedad compartida), sino las dificultades que presenta para nosotros, simples humanos, que luego tenemos que descifrar lo que está sucediendo.
Matthieu M.
Sin embargo, adoptar este enfoque limitará severamente la capacidad de un programador para aplicar patrones de programación de concurrencia en la mayoría de las clases de OOP no triviales (debido a la no capacidad de copia). Este problema se plantea en "Going Native 2013".
rwong
48

El mundo en el que vive Bjarne es muy ... académico, a falta de un término mejor. Si su código puede diseñarse y estructurarse de modo que los objetos tengan jerarquías relacionales muy deliberadas, de modo que las relaciones de propiedad sean rígidas e inflexibles, el código fluye en una dirección (de alto nivel a bajo nivel), y los objetos solo hablan con aquellos de nivel inferior. la jerarquía, entonces no encontrarás mucha necesidad shared_ptr. Es algo que usas en esas raras ocasiones en las que alguien tiene que romper las reglas. Pero, de lo contrario, puede pegar todo en vectors u otras estructuras de datos que usen semántica de valores, y unique_ptrs para cosas que tiene que asignar individualmente.

Si bien es un gran mundo para vivir, no es lo que puedes hacer todo el tiempo. Si no puede organizar su código de esa manera, porque el diseño del sistema que está tratando de hacer significa que es imposible (o simplemente profundamente desagradable), entonces encontrará que necesita cada vez más la propiedad compartida de los objetos. .

En dicho sistema, mantener punteros desnudos no es ... exactamente peligroso, pero plantea preguntas. Lo bueno de esto shared_ptres que proporciona garantías sintácticas razonables sobre la vida útil del objeto. ¿Se puede romper? Por supuesto. Pero la gente también puede const_castcosas; El cuidado básico y la alimentación shared_ptrdeben proporcionar una calidad de vida razonable para los objetos asignados cuya propiedad debe ser compartida.

Luego, hay weak_ptrs, que no se pueden usar en ausencia de a shared_ptr. Si su sistema está rígidamente estructurado, puede almacenar un puntero desnudo a algún objeto, seguro sabiendo que la estructura de la aplicación asegura que el objeto señalado lo sobrevivirá. Puede llamar a una función que devuelve un puntero a algún valor interno o externo (busque un objeto llamado X, por ejemplo). En un código estructurado adecuadamente, esa función solo estaría disponible si la vida útil del objeto fuera superior a la suya; por lo tanto, almacenar ese puntero desnudo en su objeto está bien.

Dado que esa rigidez no siempre es posible de lograr en sistemas reales, necesita alguna forma de garantizar razonablemente la vida útil. A veces, no necesitas la propiedad total; a veces, solo necesita saber cuándo el puntero es malo o bueno. Ahí es donde weak_ptrentra. Ha habido casos en los que podría haber usado un unique_ptro boost::scoped_ptr, pero tuve que usar un shared_ptrporque específicamente necesitaba darle a alguien un puntero "volátil". Un puntero cuya vida fue indeterminada, y podrían preguntar cuándo se destruyó ese puntero.

Una forma segura de sobrevivir cuando el estado del mundo es indeterminado.

¿Podría haber sido hecho por alguna llamada de función para obtener el puntero, en lugar de vía weak_ptr? Sí, pero eso podría romperse más fácilmente. Una función que devuelve un puntero desnudo no tiene forma de sugerir sintácticamente que el usuario no haga algo como almacenar ese puntero a largo plazo. Devolver un shared_ptrtambién hace que sea demasiado fácil para alguien simplemente almacenarlo y potencialmente prolongar la vida útil de un objeto. La devolución de un weak_ptrembargo fuertemente sugiere que el almacenamiento de la shared_ptrque se obtiene de lockuna ... idea dudosa. No le impedirá hacerlo, pero nada en C ++ le impide romper el código. weak_ptrproporciona una resistencia mínima al hacer lo natural.

Ahora, eso no quiere decir que shared_ptrno se pueda usar en exceso ; Ciertamente puede. Especialmente antes unique_ptr, hubo muchos casos en los que simplemente utilicé un boost::shared_ptrporque necesitaba pasar un puntero RAII o ponerlo en una lista. Sin movimiento semántico y unique_ptr, boost::shared_ptrfue la única solución real.

Y puede usarlo en lugares donde es bastante innecesario. Como se indicó anteriormente, la estructura de código adecuada puede eliminar la necesidad de algunos usos de shared_ptr. Pero si su sistema no puede estructurarse como tal y sigue haciendo lo que necesita, shared_ptrserá de gran utilidad.

Nicol Bolas
fuente
44
+1: Mira, por ejemplo, boost :: asio. Creo que la idea se extiende a muchas áreas, es posible que no sepa en el momento de la compilación qué widget UI o llamada asincrónica es la última en abandonar un objeto, y con shared_ptr no necesita saber. Obviamente no se aplica a todas las situaciones, solo otra herramienta (muy útil) en la caja de herramientas.
Guy Sirton
3
Un comentario un poco tarde; shared_ptres ideal para sistemas donde c ++ se integró con lenguaje de script como python. Usando boost::python, el recuento de referencias en el lado de c ++ y python coopera enormemente; cualquier objeto de c ++ puede mantenerse en Python y no morirá.
eudoxos
1
Solo como referencia, entiendo que ni WebKit ni Chromium usan shared_ptr. Ambos usan sus propias implementaciones de intrusive_ptr. Solo menciono eso porque ambos son ejemplos del mundo real de grandes aplicaciones escritas en C ++
gman
1
@gman: Encuentro su comentario muy engañoso, ya que la objeción de Stroustrup se shared_ptraplica igualmente a intrusive_ptr: él está objetando todo el concepto de propiedad compartida, no a ninguna ortografía específica del concepto. Entonces, para los propósitos de esta pregunta, esos son dos ejemplos del mundo real de grandes aplicaciones que usan shared_ptr. (Y, lo que es más, demuestran que shared_ptres útil incluso cuando no permite weak_ptr.)
ruakh
1
FWIW, para contrarrestar la afirmación de que Bjarne está viviendo en el mundo académico: en toda mi carrera puramente industrial (que incluyó la co-arquitectura de una bolsa de valores del G20 y únicamente la arquitectura de un MOG de 500K jugadores), solo he visto 3 casos en los que realmente necesitábamos propiedad compartida. Estoy 200% con Bjarne aquí.
No-Bugs Hare
37

No creo haberlo usado nunca std::shared_ptr.

La mayoría de las veces, un objeto está asociado con alguna colección, a la que pertenece durante toda su vida útil. En cuyo caso solo puede usar whatever_collection<o_type>o whatever_collection<std::unique_ptr<o_type>>, esa colección es un miembro de un objeto o una variable automática. Por supuesto, si no necesita un número dinámico de objetos, puede usar una matriz automática de tamaño fijo.

Ninguna iteración a través de la colección o cualquier otra operación en el objeto requiere una función auxiliar para compartir la propiedad ... usa el objeto, luego regresa, y la persona que llama garantiza que el objeto permanezca vivo durante toda la llamada . Este es, con mucho, el contrato más utilizado entre la persona que llama y la persona que llama.


Nicol Bolas comentó que "si algún objeto se aferra a un puntero desnudo y ese objeto muere ... ¡vaya!". y "Los objetos necesitan asegurarse de que el objeto viva a través de la vida de ese objeto. Solo shared_ptreso puede hacerlo".

No compro ese argumento. Al menos eso no shared_ptrresuelve este problema. Qué pasa:

  • Si alguna tabla hash se aferra a un objeto y el código hash de ese objeto cambia ... oops.
  • Si alguna función está iterando un vector y se inserta un elemento en ese vector ... ¡Vaya!

Al igual que la recolección de basura, el uso predeterminado de shared_ptralienta al programador a no pensar en el contrato entre objetos, o entre la función y la persona que llama. Es necesario pensar en las precondiciones y postcondiciones correctas, y la vida útil de los objetos es solo una pequeña parte de ese pastel más grande.

Los objetos no "mueren", un fragmento de código los destruye. Y lanzar shared_ptrel problema en lugar de resolver el contrato de llamada es una falsa seguridad.

Ben Voigt
fuente
17
@ronag: Sospecho que has comenzado a usarlo donde un puntero en bruto habría sido mejor, porque "los punteros en bruto son malos". Pero los punteros crudos no son malos . Solo hacer que el primer puntero propietario de un objeto sea un puntero sin formato es malo, porque luego debe administrar manualmente la memoria, que no es trivial en presencia de excepciones. Pero usar punteros en bruto como controladores o iteradores está bien.
Ben Voigt
44
@BenVoigt: Por supuesto, la dificultad para pasar punteros desnudos es que no conoces la vida útil de los objetos. Si algún objeto se aferra a un puntero desnudo y ese objeto muere ... ¡Uy! Ese es exactamente el tipo de cosas shared_ptry weak_ptrfueron diseñados para evitar. Bjarne trata de vivir en un mundo donde todo tiene una vida agradable y explícita, y todo se basa en eso. Y si puedes construir ese mundo, genial. Pero no es así en el mundo real. Los objetos necesitan asegurarse de que el objeto viva a través de la vida de ese objeto. Solo shared_ptrpuede hacer eso.
Nicol Bolas
55
@NicolBolas: Eso es falsa seguridad. Si la persona que llama a una función no proporciona la garantía habitual: "Este objeto no será tocado por ninguna persona externa durante la llamada a la función", ambos deben acordar qué tipo de modificaciones externas están permitidas. shared_ptrsolo mitiga una modificación externa específica, y ni siquiera la más común. Y no es responsabilidad del objeto garantizar que su vida útil sea correcta, si el contrato de llamada de función especifica lo contrario.
Ben Voigt
66
@NicolBolas: si una función crea un objeto y lo devuelve por puntero, debería ser un unique_ptr, expresando que solo existe un puntero al objeto y que tiene propiedad.
Ben Voigt
66
@Nicol: Si está buscando un puntero en alguna colección, probablemente debería usar cualquier tipo de puntero que esté en esa colección, o un puntero sin formato si la colección contiene valores. Si está creando un objeto y la persona que llama quiere un shared_ptr, aún debe devolver a unique_ptr. La conversión de unique_ptra shared_ptres fácil, pero lo contrario es lógicamente imposible.
Ben Voigt
16

Prefiero no pensar en términos absolutos (como "último recurso") sino en relación con el dominio del problema.

C ++ puede ofrecer diferentes formas de administrar la vida útil. Algunos de ellos intentan volver a conducir los objetos de forma apilada. Otros intentan escapar de esta limitación. Algunos de ellos son "literales", otros son aproximaciones.

En realidad puedes:

  1. use semántica de valor puro . Funciona para objetos relativamente pequeños donde lo importante son los "valores" y no las "identidades", donde se puede suponer que dos que Persontienen lo mismo nameson la misma persona (mejor: dos representaciones de una misma persona ). La pila de máquinas concede toda la vida, y el final -esencialmente- no importa para el programa (dado que una persona es su nombre , no importa lo que lo Personlleve)
  2. usar objetos asignados a la pila y referencias o punteros relacionados: permite el polimorfismo y otorga vida útil a los objetos. No necesita "punteros inteligentes", ya que se asegura de que ningún objeto pueda ser "apuntado" por estructuras que se quedan en la pila más tiempo que el objeto al que apuntan (primero cree el objeto, luego las estructuras que se refieren a él).
  3. use objetos asignados de montón administrado por pila : esto es lo que hacen std :: vector y todos los contenedores, y wat lo std::unique_ptrhace (puede pensarlo como un vector con tamaño 1). Nuevamente, admite que el objeto comienza a existir (y termina su existencia) antes (después) de la estructura de datos a la que se refieren.

La debilidad de este método es que los tipos y cantidades de objetos no pueden variar durante la ejecución de llamadas de nivel de pila más profundas con respecto a dónde se crean. Todas estas técnicas "fallan" su fuerza en toda la situación en la que la creación y eliminación de objetos son consecuencia de las actividades del usuario, por lo que el tipo de tiempo de ejecución del objeto no se conoce en tiempo de compilación y puede haber sobreestructuras que se refieren a objetos que el usuario solicita eliminar de una llamada de función de nivel de pila más profunda. En estos casos, debe:

  • introducir alguna disciplina sobre la gestión de objetos y estructuras de referencia relacionadas o ...
  • ir de alguna manera al lado oscuro de "escapar de la vida útil basada en la pila pura": el objeto debe salir independientemente de las funciones que los crearon. Y debe irse ... hasta que sean necesarios .

C ++ isteslf no tiene ningún mecanismo nativo para monitorear ese evento ( while(are_they_needed)), por lo tanto, debe aproximarse con:

  1. usar propiedad compartida : la vida de los objetos está vinculada a un "contador de referencia": funciona si la "propiedad" se puede organizar jerárquicamente, falla donde pueden existir bucles de propiedad. Esto es lo que hace std :: shared_ptr. Y weak_ptr se puede usar para romper el ciclo. Esto funciona la mayor parte del tiempo, pero falla en el diseño grande, donde muchos diseñadores trabajan en diferentes equipos y no hay una razón clara (algo que provenga de un requisito un tanto) sobre quién debe poseer qué (el ejemplo típico son las cadenas de me gusta dual: es el anterior debido al siguiente referente al anterior o al siguiente propietario del anterior referente al siguiente? En ausencia de un requisito, las soluciones son equivalentes, y en un gran proyecto corre el riesgo de mezclarlas)
  2. Use un montón de recolección de basura : simplemente no le importa la vida. Ejecutas el colector de vez en cuando y lo que no se alcanza se considera "ya no es necesario" y ... bueno ... ejem ... ¿destruido? ¿finalizado? ¿congelado?. Hay varios colectores GC, pero nunca encuentro uno que sea realmente consciente de C ++. La mayoría de ellos liberan memoria, sin preocuparse por la destrucción de objetos.
  3. Utilice un recolector de basura compatible con C ++ , con una interfaz de métodos estándar adecuada. Buena suerte para encontrarlo.

Yendo a la primera solución a la última, la cantidad de estructura de datos auxiliares requerida para administrar la vida útil del objeto aumenta, a medida que pasa el tiempo para organizarla y mantenerla.

El recolector de basura tiene un costo, shared_ptr tiene menos, unique_ptr aún menos, y los objetos gestionados de pila tienen muy pocos.

¿Es shared_ptrel "último recurso"? No, no lo es: el último recurso son los recolectores de basura. shared_ptres en realidad el std::último recurso propuesto. Pero puede ser la solución correcta, si está en la situación que le expliqué.

Emilio Garavaglia
fuente
9

La única cosa mencionada por Herb Sutter en una sesión posterior es que cada vez que copia una copia shared_ptr<>hay un incremento / decremento entrelazado que tiene que suceder. En el código de subprocesos múltiples en un sistema de múltiples núcleos, la sincronización de memoria no es insignificante. Dada la opción, es mejor usar un valor de pila o a unique_ptr<>y pasar referencias o punteros sin formato.

Eclipse
fuente
1
O pase shared_ptrpor lvalue o rvalue reference ...
ronag
8
El punto es que no solo lo use shared_ptrcomo si fuera la bala de plata que resolverá todos sus problemas de pérdida de memoria solo porque está en el estándar. Es una trampa tentadora, pero sigue siendo importante tener en cuenta la propiedad de los recursos y, a menos que esa propiedad se comparta, a shared_ptr<>no es la mejor opción.
Eclipse
Para mí este es el detalle menos importante. Ver optimización prematura. En la mayoría de los casos, esto no debería conducir la decisión.
Guy Sirton
1
@gbjbaanb: sí, están en el nivel de la CPU, pero en un sistema multinúcleo estás invalidando los cachés y forzando las barreras de memoria.
Eclipse
44
En un proyecto de juego en el que trabajé, descubrimos que la diferencia de rendimiento era muy significativa, hasta el punto en que necesitábamos 2 tipos diferentes de punteros contados por ref, uno que era seguro para hilos, uno que no lo era.
Kylotan el
7

No recuerdo si el último "recurso" fue la palabra exacta que usó, pero creo que el significado real de lo que dijo fue la última "elección": dadas condiciones claras de propiedad; unique_ptr, weak_ptr, shared_ptr e incluso los punteros desnudos tienen su lugar.

Una cosa en la que todos acordaron es que estamos (desarrolladores, autores de libros, etc.) todos en la "fase de aprendizaje" de C ++ 11 y se están definiendo patrones y estilos.

Como ejemplo, Herb explicó que deberíamos esperar nuevas ediciones de algunos de los principales libros de C ++, como Effective C ++ (Meyers) y C ++ Coding Standards (Sutter & Alexandrescu), dentro de un par de años, mientras que la experiencia de la industria y las mejores prácticas con C ++ 11 sartenes.

Eddie Velasquez
fuente
5

Creo que lo que quiere decir es que se está volviendo común que todos escriban shared_ptr cada vez que hayan escrito un puntero estándar (como una especie de reemplazo global), y que se esté utilizando como una copia en lugar de diseñar o al menos planificación para la creación y eliminación de objetos.

La otra cosa que la gente olvida (además del bloqueo / actualización / desbloqueo del cuello de botella mencionado en el material anterior), es que shared_ptr por sí solo no resuelve los problemas del ciclo. Todavía puede filtrar recursos con shared_ptr:

El objeto A, contiene un puntero compartido a otro objeto A El objeto B crea A a1 y A a2, y asigna a1.otherA = a2; y a2.otherA = a1; Ahora, los punteros compartidos del objeto B que usaba para crear a1, a2 quedan fuera de alcance (digamos al final de una función). Ahora tiene una fuga: nadie más se refiere a a1 y a2, pero se refieren entre sí, por lo que sus recuentos de referencias siempre son 1 y usted ha filtrado.

Ese es el ejemplo simple, cuando esto ocurre en código real, generalmente ocurre de manera complicada. Hay una solución con weak_ptr, pero muchas personas ahora solo hacen shared_ptr en todas partes y ni siquiera saben del problema de fuga o incluso de weak_ptr.

Para concluir: creo que los comentarios a los que hace referencia el OP se reducen a esto:

No importa en qué idioma esté trabajando (administrado, no administrado o algo intermedio con recuentos de referencias como shared_ptr), debe comprender y decidir intencionalmente la creación de objetos, vidas y destrucción.

editar: incluso si eso significa "desconocido, necesito usar un shared_ptr", todavía lo has pensado y lo estás haciendo intencionalmente.

luego
fuente
3

Contestaré desde mi experiencia con Objective-C, un lenguaje donde todos los objetos son contados por referencia y asignados en el montón. Debido a tener una forma de tratar los objetos, las cosas son mucho más fáciles para el programador. Eso ha permitido definir reglas estándar que, cuando se cumplen, garantizan la solidez del código y no hay pérdidas de memoria. También hizo posible que surgieran optimizaciones inteligentes del compilador como el ARC reciente (conteo automático de referencias).

Mi punto es que shared_ptr debería ser tu primera opción en lugar del último recurso. Use el conteo de referencias por defecto y otras opciones solo si está seguro de lo que está haciendo. Serás más productivo y tu código será más robusto.

Dimitris
fuente
1

Trataré de responder la pregunta:

¿Cómo podemos programar sin std :: shared_ptr y aún así administrar vidas de objetos de manera segura?

C ++ tiene una gran cantidad de formas diferentes de hacer memoria, por ejemplo:

  1. Use en struct A { MyStruct s1,s2; };lugar de shared_ptr en el alcance de la clase. Esto es solo para programadores avanzados porque requiere que comprenda cómo funcionan las dependencias y requiere la capacidad de controlar las dependencias lo suficiente como para restringirlas a un árbol. El orden de las clases en el archivo de encabezado es un aspecto importante de esto. Parece que este uso ya es común con los tipos nativos de c ++ incorporados, pero su uso con clases definidas por el programador parece ser menos utilizado debido a estos problemas de dependencia y orden de clases. Esta solución también tiene problemas con sizeof. Los programadores ven problemas en esto como un requisito para usar declaraciones directas o #incluidos innecesarios y, por lo tanto, muchos programadores recurrirán a una solución inferior de punteros y luego a shared_ptr.
  2. Use MyClass &find_obj(int i);+ clone () en lugar de shared_ptr<MyClass> create_obj(int i);. Muchos programadores quieren crear fábricas para crear nuevos objetos. shared_ptr es ideal para este tipo de uso. El problema es que ya asume una solución de administración de memoria compleja que utiliza la asignación de almacenamiento dinámico / libre, en lugar de una solución más simple de pila o basada en objetos. Una buena jerarquía de clases C ++ admite todos los esquemas de administración de memoria, no solo uno de ellos. La solución basada en referencias puede funcionar si el objeto devuelto se almacena dentro del objeto que lo contiene, en lugar de utilizar la variable de alcance de la función local. Se debe evitar pasar la propiedad de la fábrica al código de usuario. Copiar el objeto después de usar find_obj () es una buena forma de manejarlo: los constructores de copia normales y el constructor normal (de diferente clase) con parámetro de referencia o clon () para objetos polimórficos pueden manejarlo.
  3. Uso de referencias en lugar de punteros o shared_ptrs. Cada clase de c ++ tiene constructores, y cada miembro de datos de referencia debe inicializarse. Este uso puede evitar muchos usos de punteros y shared_ptrs. Solo necesita elegir si su memoria está dentro del objeto, o fuera de él, y elegir la solución de estructura o la solución de referencia en función de la decisión. Los problemas con esta solución generalmente están relacionados con evitar los parámetros del constructor, lo cual es una práctica común pero problemática y la incomprensión de cómo se deben diseñar las interfaces para las clases.
tp1
fuente
"Se debe evitar pasar la propiedad de la fábrica al código de usuario". ¿Y qué pasa cuando eso no es posible? "Uso de referencias en lugar de punteros o shared_ptrs". Mmm no. Los punteros se pueden volver a colocar. Las referencias no pueden. Esto fuerza restricciones de tiempo de construcción sobre lo que se almacena en una clase. Eso no es práctico para muchas cosas. Su solución parece ser muy rígida e inflexible a las necesidades de una interfaz más fluida y un patrón de uso.
Nicol Bolas
@Nicol Bolas: una vez que sigas las reglas anteriores, las referencias se usarán para las dependencias entre objetos y no para el almacenamiento de datos como sugeriste. Las dependencias son más estables que los datos, por lo que nunca nos metemos en el problema que estaba considerando.
tp1
Aquí hay un ejemplo muy simple. Tienes una entidad de juego, que es un objeto. Necesita referirse a otro objeto, que es una entidad objetivo con la que necesita hablar. Sin embargo, los objetivos pueden cambiar. Los objetivos pueden morir en varios puntos. Y la entidad necesita poder manejar estas circunstancias. Su enfoque rígido sin punteros no puede manejar ni siquiera algo tan simple como cambiar objetivos, y mucho menos que el objetivo muera.
Nicol Bolas
@nicol bolas: oh, eso se maneja de manera diferente; La interfaz de la clase admite más de una "entidad". En lugar de un mapeo 1: 1 entre objetos y entidades, usará entityarray. Luego, las entidades mueren muy fácilmente al eliminarlo de la matriz. Solo hay un pequeño número de matrices de entidades en todo el juego y las dependencias entre matrices no cambian muy a menudo :)
tp1
2
No, unique_ptres el más adecuado para fábricas. Puede convertir un unique_ptra shared_ptr, pero es lógicamente imposible ir en la otra dirección.
Ben Voigt