Como puedo ver, los punteros inteligentes se utilizan ampliamente en muchos proyectos C ++ del mundo real.
Aunque algún tipo de punteros inteligentes son obviamente beneficiosos para soportar RAII y transferencias de propiedad, también existe la tendencia de usar punteros compartidos por defecto , como una forma de "recolección de basura" , de modo que el programador no tenga que pensar tanto en la asignación. .
¿Por qué los punteros compartidos son más populares que integrar un recolector de basura adecuado como Boehm GC ? (¿O está de acuerdo en absoluto en que son más populares que los GC reales?)
Conozco dos ventajas de los GC convencionales sobre el conteo de referencias:
- Los algoritmos de GC convencionales no tienen problemas con los ciclos de referencia .
- El recuento de referencias es generalmente más lento que un GC adecuado.
¿Cuáles son las razones para usar punteros inteligentes de conteo de referencias?
fuente
std::unique_ptr
es suficiente y, como tal, tiene una sobrecarga cero sobre punteros sin procesar en términos de rendimiento en tiempo de ejecución. Al usarlo enstd::shared_ptr
todas partes, también oscurecería la semántica de propiedad, perdiendo uno de los principales beneficios de los punteros inteligentes además de la administración automática de recursos: una comprensión clara de la intención detrás del código.Respuestas:
Algunas ventajas del recuento de referencias sobre la recolección de basura:
Gastos indirectos bajos. Los recolectores de basura pueden ser bastante intrusivos (por ejemplo, hacer que su programa se congele en momentos impredecibles mientras se procesa un ciclo de recolección de basura) y bastante intensivo en memoria (por ejemplo, la huella de memoria de su proceso crece innecesariamente a muchos megabytes antes de que la recolección de basura finalmente se active)
Comportamiento más predecible. Con el recuento de referencias, tiene la garantía de que su objeto se liberará en el instante en que desaparezca la última referencia. Con la recolección de basura, por otro lado, su objeto se liberará "en algún momento", cuando el sistema lo encuentre. Para la RAM, esto no suele ser un gran problema en equipos de escritorio o servidores con poca carga, pero para otros recursos (por ejemplo, identificadores de archivos) a menudo es necesario cerrarlos lo antes posible para evitar posibles conflictos más adelante.
Más simple El recuento de referencias se puede explicar en unos minutos e implementar en una o dos horas. Los recolectores de basura, especialmente aquellos con un rendimiento decente, son extremadamente complejos y no mucha gente los entiende.
Estándar. C ++ incluye el recuento de referencias (vía shared_ptr) y amigos en el STL, lo que significa que la mayoría de los programadores de C ++ están familiarizados con él y la mayoría del código de C ++ funcionará con él. Sin embargo, no hay ningún recolector de basura C ++ estándar, lo que significa que debe elegir uno y esperar que funcione bien para su caso de uso, y si no lo hace, es su problema solucionarlo, no el idioma.
En cuanto a los supuestos inconvenientes del recuento de referencias: no detectar ciclos es un problema, pero uno con el que nunca me he encontrado personalmente en los últimos diez años al usar el recuento de referencias. La mayoría de las estructuras de datos son naturalmente acíclicas, y si se encuentra con una situación en la que necesita referencias cíclicas (por ejemplo, puntero principal en un nodo de árbol), puede usar un débil_ptr o un puntero C sin formato para la "dirección hacia atrás". Siempre que conozca el problema potencial cuando diseña sus estructuras de datos, no es un problema.
En cuanto al rendimiento, nunca he tenido un problema con el rendimiento del recuento de referencias. He tenido problemas con el rendimiento de la recolección de basura, en particular los congelamientos aleatorios en los que puede incurrir GC, a los que la única solución ("no asignar objetos") podría reformularse como "no usar GC" .
fuente
make_shared
cuando regresa. Aún así, la latencia tiende a ser el mayor problema en las aplicaciones en tiempo real, pero el rendimiento es más importante en general, por lo que el GC de rastreo se usa tanto. No sería tan rápido para hablar mal de ellos.Para obtener un buen rendimiento de un GC, el GC debe poder mover objetos en la memoria. En un lenguaje como C ++ donde puede interactuar directamente con ubicaciones de memoria, esto es prácticamente imposible. (Microsoft C ++ / CLR no cuenta porque introduce una nueva sintaxis para los punteros administrados por GC y, por lo tanto, es efectivamente un lenguaje diferente).
El Boehm GC, aunque es una idea ingeniosa, es en realidad lo peor de ambos mundos: necesita un malloc () que sea más lento que un buen GC, por lo que pierde el comportamiento determinista de asignación / desasignación sin el aumento de rendimiento correspondiente de un GC generacional . Además, es necesariamente conservador, por lo que no necesariamente recogerá toda su basura de todos modos.
Un GC bien ajustado puede ser una gran cosa. Pero en un lenguaje como C ++, las ganancias son mínimas y los costos a menudo simplemente no valen la pena.
Sin embargo, será interesante ver que a medida que C ++ 11 se vuelve más popular, si las lambdas y la semántica de captura comienzan a conducir a la comunidad C ++ hacia los mismos tipos de problemas de asignación y vida útil de los objetos que causaron que la comunidad Lisp inventara GC en el primer sitio.
Vea también mi respuesta a una pregunta relacionada en StackOverflow .
fuente
Es cierto, pero objetivamente, la gran mayoría del código ahora está escrito en idiomas modernos con rastreadores de basura.
Esa es una mala idea porque aún debe preocuparse por los ciclos.
Oh wow, hay muchas cosas mal con tu línea de pensamiento:
El GC de Boehm no es un GC "adecuado" en ningún sentido de la palabra. Es realmente horrible Es conservador, por lo que tiene fugas y es ineficiente por diseño. Ver: http://flyingfrogblog.blogspot.co.uk/search/label/boehm
Los punteros compartidos, objetivamente, no son tan populares como GC porque la gran mayoría de los desarrolladores están usando lenguajes GC'd ahora y no necesitan punteros compartidos. Basta con mirar a Java y Javascript en el mercado laboral en comparación con C ++.
Parece que está restringiendo la consideración a C ++ porque, supongo, cree que GC es un problema tangencial. No lo es (la única forma de obtener un GC decente es diseñar el lenguaje y la VM desde el principio), por lo que está introduciendo un sesgo de selección. Las personas que realmente quieren una recolección de basura adecuada no se quedan con C ++.
Está restringido a C ++ pero desearía tener una administración de memoria automática.
fuente
En MacOS X e iOS, y con los desarrolladores que usan Objective-C o Swift, el recuento de referencias es popular porque se maneja automáticamente, y el uso de recolección de basura ha disminuido considerablemente ya que Apple ya no lo admite (me han dicho que las aplicaciones usan la recolección de basura se interrumpirá en la próxima versión de MacOS X, y la recolección de basura nunca se implementó en iOS). De hecho, dudo seriamente de que haya mucho software utilizando recolección de basura cuando estaba disponible.
La razón para deshacerse de la recolección de basura: nunca funcionó de manera confiable en un entorno de estilo C donde los punteros podrían "escapar" a áreas no accesibles para el recolector de basura. Apple cree firmemente y cree que el conteo de referencias es más rápido. (Puede hacer cualquier reclamo aquí sobre la velocidad relativa, pero nadie ha podido convencer a Apple). Y al final, nadie usó la recolección de basura.
Lo primero que aprende cualquier desarrollador de MacOS X o iOS es cómo manejar los ciclos de referencia, por lo que no es un problema para un desarrollador real.
fuente
La mayor desventaja de la recolección de basura en C ++ es que es imposible hacerlo bien:
En C ++, los punteros no viven en su propia comunidad amurallada, se mezclan con otros datos. Como tal, no puede distinguir un puntero de otros datos que simplemente tienen un patrón de bits que puede interpretarse como un puntero válido.
Consecuencia: Cualquier recolector de basura C ++ filtrará objetos que deberían ser recolectados.
En C ++, puede hacer aritmética de puntero para derivar punteros. Como tal, si no encuentra un puntero al comienzo de un bloque, eso no significa que no se pueda hacer referencia a ese bloque.
Consecuencia: cualquier recolector de basura C ++ tiene que tener en cuenta estos ajustes, tratando cualquier secuencia de bits que apunte a cualquier lugar dentro de un bloque, incluso justo después del final, como un puntero válido que hace referencia al bloque.
Nota: Ningún recolector de basura C ++ puede manejar código con trucos como estos:
Es cierto que esto invoca un comportamiento indefinido. Pero parte del código existente es más inteligente de lo que es bueno para él, y puede provocar la desasignación preliminar por parte de un recolector de basura.
fuente