c++ - recogen - recolectores de basura particulares
¿Por qué C++ no tiene un recolector de basura? (16)
No estoy haciendo esta pregunta debido a los méritos de la recolección de basura en primer lugar. Mi principal razón para preguntar esto es que sé que Bjarne Stroustrup ha dicho que C ++ tendrá un recolector de basura en algún momento.
Dicho esto, ¿por qué no se ha añadido? Ya hay algunos recolectores de basura para C ++. ¿Es esta una de esas cosas de tipo "más fácil decirlo que hacerlo"? ¿O hay otras razones por las que no se ha agregado (y no se agregará en C ++ 11)?
Enlaces cruzados:
Solo para aclarar, entiendo las razones por las que C ++ no tenía un recolector de basura cuando se creó por primera vez. Me pregunto por qué no se puede agregar el colector.
Porque en estos días, C ++ ya no lo necesita.
La situación, para el código escrito en estos días (C ++ 17 y siguiendo las pautas de codificación del idioma oficial ) es la siguiente:
- La mayoría del código relacionado con la propiedad de la memoria está en las bibliotecas (especialmente las que proporcionan contenedores).
- La mayoría del uso del código que involucra la propiedad de la memoria sigue el pattern RAII , por lo que la asignación se realiza en la construcción y la desasignación en la destrucción, lo que sucede al salir del ámbito en el que se asignó algo.
- Usted no asigna o desasigna explícitamente la memoria directamente .
- Los punteros sin procesar no poseen memoria (si has seguido las pautas), por lo que no puedes filtrarlos.
- Si se pregunta cómo va a pasar las direcciones de inicio de las secuencias de valores en la memoria, lo hará con un span ; no se necesita puntero en bruto.
- Si realmente necesita un "puntero" de propiedad, use los punteros inteligentes de la biblioteca estándar de C ++ : no pueden filtrarse y son bastante eficientes. Alternativamente, puede pasar la propiedad a través de los límites del alcance con "punteros de propietario" . Estos son poco comunes y deben ser utilizados explícitamente; y permiten la comprobación estática parcial contra fugas.
"Oh, sí? Pero ¿qué pasa con ...
... si escribo código de la forma en que hemos escrito C ++ hasta ahora?
De hecho, puede simplemente ignorar todas las pautas y escribir el código de la aplicación con fugas, que se compilará y ejecutará (y filtrará), como siempre.
Pero no es una situación de "simplemente no hagas eso", donde se espera que el desarrollador sea virtuoso y ejerza mucho autocontrol; simplemente no es más sencillo escribir código no conforme, ni es más rápido de escribir, ni tiene un mejor rendimiento. Gradualmente, también se volverá más difícil de escribir, ya que se enfrentaría a una creciente "falta de coincidencia de impedancia" con lo que el código conforme proporciona y espera.
... si yo reintrepret_cast
? ¿O aritmética puntero? ¿O otros hacks similares?
De hecho, si te lo propones, puedes escribir código que arruine las cosas a pesar de jugar bien con las pautas. Pero:
- Haría esto raramente (en términos de lugares en el código, no necesariamente en términos de fracción del tiempo de ejecución)
- Solo harías esto intencionalmente, no accidentalmente.
- Al hacerlo se destacará en una base de código conforme a las directrices.
- Es el tipo de código en el que, de todos modos, omitiría el GC en otro idioma.
... desarrollo de bibliotecas? "
Si usted es un desarrollador de bibliotecas de C ++, entonces escribe un código inseguro que incluye punteros en bruto, y debe codificar de manera cuidadosa y responsable, pero estas son piezas de código independientes escritas por expertos.
En resumen, no hay realmente ninguna motivación para recolectar basura, como todos ustedes, pero asegúrese de no producir basura. GC está en camino de convertirse en un no problema con C ++.
Esto no quiere decir que GC no sea un problema interesante para ciertas aplicaciones específicas, cuando se desea emplear estrategias de asignación y desasignaciones personalizadas. Para aquellos que desearían asignación y desasignación personalizadas, no un GC de nivel de idioma.
¿Que tipo? ¿Debería optimizarse para controladores de lavadora, teléfonos celulares, estaciones de trabajo o supercomputadoras?
¿Debería priorizar la capacidad de respuesta de la interfaz gráfica de usuario o la carga del servidor?
¿Debería usar mucha memoria o mucha CPU?
C / c ++ se usa en demasiadas circunstancias diferentes. Sospecho que algo como impulsar los punteros inteligentes será suficiente para la mayoría de los usuarios
Edición: los recolectores automáticos de basura no son tanto un problema de rendimiento (siempre puedes comprar más servidores) es una cuestión de rendimiento predecible.
No saber cuándo arrancará el GC es como emplear un piloto de aerolínea narcoléptica, la mayoría de las veces son geniales, ¡pero cuando realmente se necesita capacidad de respuesta!
Aunque esta es una pregunta antigua , todavía hay un problema que no veo que nadie haya abordado: la recolección de basura es casi imposible de especificar.
En particular, el estándar de C ++ es bastante cuidadoso para especificar el lenguaje en términos de comportamiento observable externamente, en lugar de cómo la implementación logra ese comportamiento. En el caso de la recolección de basura, sin embargo, prácticamente no hay un comportamiento observable externamente.
La idea general de la recolección de basura es que debe hacer un esfuerzo razonable para garantizar que la asignación de memoria se realice correctamente. Desafortunadamente, es esencialmente imposible garantizar que cualquier asignación de memoria tenga éxito, incluso si tiene un recolector de basura en funcionamiento. Esto es cierto hasta cierto punto en cualquier caso, pero particularmente en el caso de C ++, porque (probablemente) no es posible usar un colector de copias (o algo similar) que mueva objetos en la memoria durante un ciclo de recolección.
Si no puede mover objetos, no puede crear un espacio de memoria único y contiguo desde el cual hacer sus asignaciones, y eso significa que su montón (o tienda libre, o como prefiera llamarlo) puede, y probablemente lo hará. , se fragmentan con el tiempo. Esto, a su vez, puede evitar que una asignación tenga éxito, incluso cuando hay más memoria libre que la cantidad solicitada.
Si bien es posible ofrecer alguna garantía que diga (en esencia) que si repite el mismo patrón de asignación repetidamente, y tuvo éxito la primera vez, continuará teniendo éxito en las siguientes iteraciones, siempre que la memoria asignada Se hizo inaccesible entre iteraciones. Esa es una garantía tan débil que es esencialmente inútil, pero no veo ninguna esperanza razonable de fortalecerla.
Aun así, es más fuerte de lo que se ha propuesto para C ++. La propuesta anterior [advertencia: PDF] (que se eliminó) no garantizaba nada en absoluto. En 28 páginas de la propuesta, lo que se encontró en el camino del comportamiento observable externamente fue una nota única (no normativa) que decía:
[Nota: para los programas de recolección de basura, una implementación alojada de alta calidad debe intentar maximizar la cantidad de memoria inalcanzable que reclama. "Nota final"
Al menos para mí, esto plantea una pregunta seria sobre el retorno de la inversión. Vamos a descifrar el código existente (nadie está seguro de cuánto, pero definitivamente un poco), impondremos nuevos requisitos en las implementaciones y nuevas restricciones en el código, y lo que obtenemos a cambio ¿posiblemente no sea nada?
Incluso en el mejor de los casos, lo que obtenemos son programas que, según las pruebas con Java , probablemente requerirán alrededor de seis veces más memoria para ejecutarse a la misma velocidad que lo hacen ahora. Peor aún, la recolección de basura fue parte de Java desde el principio: C ++ impone suficientes restricciones en el recolector de basura, por lo que casi con certeza tendrá una relación costo / beneficio aún peor (incluso si vamos más allá de lo que garantiza la propuesta y asumimos que algún beneficio).
Resumiría matemáticamente la situación: esta es una situación compleja. Como cualquier matemático sabe, un número complejo tiene dos partes: real e imaginario. Me parece que lo que tenemos aquí son costos reales, pero beneficios que son (al menos en su mayoría) imaginarios.
La idea detrás de C ++ era que no pagaría ningún impacto en el rendimiento por las funciones que no usa. Por lo tanto, agregar recolección de basura hubiera significado tener algunos programas que se ejecutan directamente en el hardware como lo hace C y otros dentro de algún tipo de máquina virtual en tiempo de ejecución.
Nada le impide utilizar algún tipo de punteros inteligentes vinculados a algún mecanismo de recolección de basura de terceros. Me parece recordar que Microsoft hizo algo así con COM y no me fue bien.
La recolección de basura implícita podría haberse agregado, pero simplemente no hizo el corte. Probablemente no solo debido a las complicaciones de la implementación, sino también porque las personas no pueden llegar a un consenso general lo suficientemente rápido.
Una cita del propio Bjarne Stroustrup:
Tenía la esperanza de que un recolector de basura que pudiera habilitarse opcionalmente fuera parte de C ++ 0x, pero había suficientes problemas técnicos que tengo que resolver con solo una especificación detallada de cómo dicho recolector se integra con el resto del lenguaje , si se proporciona. Como es el caso con esencialmente todas las características de C ++ 0x, existe una implementación experimental.
Hay una buena discusión del tema here .
Visión general:
C ++ es muy poderoso y te permite hacer casi cualquier cosa. Por esta razón, no le impone automáticamente muchas cosas que podrían afectar el rendimiento. La recolección de basura se puede implementar fácilmente con punteros inteligentes (objetos que envuelven punteros con un recuento de referencia, que se eliminan automáticamente cuando el recuento de referencia llega a 0).
C ++ fue construido teniendo en cuenta a los competidores que no tenían recolección de basura. La eficiencia fue la principal preocupación de la cual C ++ tuvo que defenderse de las críticas en comparación con C y otros.
Hay 2 tipos de recolección de basura ...
Recogida de basura explícita:
C ++ 0x tendrá recolección de basura mediante punteros creados con shared_ptr
Si lo desea, puede usarlo, si no lo desea, no está obligado a usarlo.
Actualmente puede usar boost: shared_ptr también si no desea esperar a C ++ 0x.
Recogida de basura implícita:
Aunque no tiene recolección de basura transparente. Sin embargo, será un punto de enfoque para futuras especificaciones de C ++.
¿Por qué Tr1 no tiene recolección de basura implícita?
Hay muchas cosas que debió haber tenido tr1 de C ++ 0x, Bjarne Stroustrup, en entrevistas anteriores, dijo que tr1 no tenía todo lo que le hubiera gustado.
Para añadir al debate aquí.
Hay problemas conocidos con la recolección de basura, y su comprensión ayuda a comprender por qué no hay ninguno en C ++.
1. ¿Rendimiento?
La primera queja es a menudo sobre el rendimiento, pero la mayoría de las personas realmente no se dan cuenta de lo que están hablando. Como lo ilustra Martin Beckett
el problema puede no ser el rendimiento per se, sino la previsibilidad del rendimiento.
Actualmente hay 2 familias de GC que están ampliamente desplegadas:
- Tipo de marca y barrido
- Tipo de referencia de conteo
Mark And Sweep
es más rápido (menos impacto en el rendimiento general) pero sufre de un síndrome de "congelación del mundo": es decir, cuando el GC se activa, todo lo demás se detiene hasta que el GC haya realizado su limpieza. Si desea construir un servidor que responda en unos pocos milisegundos ... algunas transacciones no estarán a la altura de sus expectativas :)
El problema del Reference Counting
de Reference Counting
es diferente: el recuento de referencias agrega sobrecarga, especialmente en entornos de Reference Counting
múltiple porque necesita tener un recuento atómico. Además, existe el problema de los ciclos de referencia, por lo que necesita un algoritmo inteligente para detectar esos ciclos y eliminarlos (generalmente implementarse también mediante un "congelamiento del mundo", aunque menos frecuente). En general, a partir de hoy, este tipo (aunque normalmente responde mejor o, con menos frecuencia, se congela) es más lento que el Mark And Sweep
.
He visto un artículo de implementadores de Eiffel que intentaban implementar un recolector de basura con Reference Counting
que tendría un rendimiento global similar al de Mark And Sweep
sin el aspecto "Congelar el mundo". Se requería un hilo separado para el GC (típico). El algoritmo fue un poco aterrador (al final), pero el documento hizo un buen trabajo al presentar los conceptos uno por uno y mostrar la evolución del algoritmo desde la versión "simple" hasta la versión completa. Lectura recomendada si solo pudiera volver a poner mis manos sobre el archivo PDF ...
2. La adquisición de recursos es la inicialización
Es un lenguaje común en C++
que envolverá la propiedad de los recursos dentro de un objeto para garantizar que se liberen correctamente. Se usa principalmente para la memoria ya que no tenemos recolección de basura, pero también es útil para muchas otras situaciones:
- bloqueos (multihilo, manejador de archivos, ...)
- conexiones (a una base de datos, a otro servidor, ...)
La idea es controlar adecuadamente la vida útil del objeto:
- Debería estar vivo mientras lo necesites.
- debe ser matado cuando hayas terminado con eso
El problema de GC es que si ayuda con el primero y, en última instancia, garantiza que más adelante ... este "último" puede no ser suficiente. Si libera un bloqueo, realmente le gustaría que se liberara ahora, ¡para que no bloquee más llamadas!
Los idiomas con GC tienen dos entornos de trabajo:
- no use GC cuando la asignación de pila es suficiente: normalmente es por problemas de rendimiento, pero en nuestro caso es realmente útil ya que el alcance define la vida útil
-
using
construcción ... pero es explícito (débil) RAII mientras que en C ++ RAII está implícito, por lo que el usuario NO PUEDE cometer el error de manera involuntaria (omitiendo la palabra claveusing
)
3. Punteros inteligentes
Los punteros inteligentes a menudo aparecen como una bala de plata para manejar la memoria en C++
. Muchas veces he escuchado: después de todo, no necesitamos GC, ya que tenemos punteros inteligentes.
Uno no podría estar más equivocado.
Los punteros inteligentes ayudan: auto_ptr
y unique_ptr
utilizan conceptos RAII, de hecho extremadamente útiles. Son tan simples que puedes escribirlos por ti mismo fácilmente.
Sin embargo, cuando se necesita compartir la propiedad, se vuelve más difícil: puede compartir entre varios subprocesos y hay algunos problemas sutiles con el manejo del conteo. Por lo tanto, uno naturalmente va hacia shared_ptr
.
Es genial, eso es para lo que Boost, después de todo, pero no es una bala de plata. De hecho, el problema principal con shared_ptr
es que emula un GC implementado por el Reference Counting
pero usted necesita implementar la detección de ciclos por usted mismo ... Urg
Por supuesto, existe este punto weak_ptr
, pero lamentablemente ya he visto pérdidas de memoria a pesar del uso de shared_ptr
debido a esos ciclos ... y cuando estás en un entorno de múltiples hilos, ¡es extremadamente difícil de detectar!
4. ¿Cuál es la solución?
No hay una bala de plata, pero como siempre, es definitivamente factible. En ausencia de GC, uno debe ser claro sobre la propiedad:
- Prefiero tener un solo dueño en un momento dado, si es posible
- si no, asegúrate de que tu diagrama de clase no tenga ningún ciclo relacionado con la propiedad y
weak_ptr
con la aplicación sutil deweak_ptr
De hecho, sería genial tener un GC ... sin embargo, no es un problema trivial. Y mientras tanto, solo tenemos que arremangarnos.
Para responder la mayoría de las preguntas "por qué" sobre C ++, lea Diseño y evolución de C ++
Si desea la recolección automática de basura, hay buenos recolectores de basura comerciales y de dominio público para C ++. Para aplicaciones donde la recolección de basura es adecuada, C ++ es un excelente lenguaje de recolección de basura con un rendimiento que se compara favorablemente con otros idiomas de recolección de basura. Consulte El lenguaje de programación de C ++ (3ª edición) para una discusión sobre la recolección automática de basura en C ++. Véase también, Hans-J. El sitio de Boehm para la recolección de basura C y C ++. Además, C ++ admite técnicas de programación que permiten que la administración de la memoria sea segura e implícita sin un recolector de basura.
Fuente: http://www.stroustrup.com/bs_faq.html#garbage-collection
En cuanto a por qué no lo tiene incorporado, si recuerdo correctamente, se inventó antes de que GC fuera el objetivo , y no creo que el lenguaje haya tenido GC por varias razones (compatibilidad con IE Backwards con C)
Espero que esto ayude.
Stroustrup hizo algunos buenos comentarios al respecto en la conferencia Going Native 2013.
Solo salta a unos 25m50 en este video . (Recomendaría ver el video completo en realidad, pero esto omite las cosas sobre la recolección de basura).
Cuando tiene un lenguaje realmente bueno que lo hace fácil (y seguro, predecible, fácil de leer y fácil de enseñar) para tratar los objetos y valores de manera directa, evitando el uso (explícito) de montón, entonces ni siquiera quieres recolección de basura.
Con el C ++ moderno y las cosas que tenemos en C ++ 11, la recolección de basura ya no es deseable, excepto en circunstancias limitadas. De hecho, incluso si un buen recolector de basura está integrado en uno de los principales compiladores de C ++, creo que no se utilizará muy a menudo. Será más fácil , no más difícil, evitar el GC.
Él muestra este ejemplo:
void f(int n, int x) {
Gadget *p = new Gadget{n};
if(x<100) throw SomeException{};
if(x<200) return;
delete p;
}
Esto es inseguro en C ++. ¡Pero también es inseguro en Java! En C ++, si la función regresa antes, la delete
nunca será llamada. Pero si tuvo una colección de basura completa, como en Java, simplemente recibirá una sugerencia de que el objeto se destruirá "en algún momento en el futuro" ( Actualización: es aún peor que esto. Java no promete llamar al finalizador nunca - tal vez nunca se llamará). Esto no es suficiente si Gadget tiene un identificador de archivo abierto, o una conexión a una base de datos, o los datos que ha almacenado temporalmente en una base de datos en un momento posterior. Queremos que el Gadget se destruya tan pronto como se termine, para liberar estos recursos lo antes posible. No quiere que su servidor de base de datos tenga dificultades con miles de conexiones de base de datos que ya no son necesarias, no sabe que su programa ha terminado de funcionar.
Entonces, ¿cuál es la solución? Hay algunos enfoques. El enfoque obvio, que utilizará para la gran mayoría de sus objetos es:
void f(int n, int x) {
Gadget p = {n}; // Just leave it on the stack (where it belongs!)
if(x<100) throw SomeException{};
if(x<200) return;
}
Esto requiere menos caracteres para escribir. No tiene new
estorbos. No requiere que Gadget
dos veces. El objeto se destruye al final de la función. Si esto es lo que quieres, esto es muy intuitivo. Gadget
comportan igual que int
o double
. Predecible, fácil de leer, fácil de enseñar. Todo es un ''valor''. A veces es un gran valor, pero los valores son más fáciles de enseñar porque no tiene esta cosa de ''acción a distancia'' que se obtiene con punteros (o referencias).
La mayoría de los objetos que creas se usan solo en la función que los creó, y tal vez se pasan como entradas a funciones secundarias. El programador no debería tener que pensar en la "administración de memoria" cuando devuelve objetos, o si no comparte objetos en partes del software muy separadas.
El alcance y la vida son importantes. La mayoría de las veces, es más fácil si la vida útil es la misma que el alcance. Es más fácil de entender y más fácil de enseñar. Cuando desee una vida diferente, debería ser obvio leer el código que está haciendo, mediante el uso de shared_ptr
por ejemplo. (O devolver objetos (grandes) por valor, aprovechando move-semantics o unique_ptr
.
Esto puede parecer un problema de eficiencia. ¿Qué sucede si deseo devolver un gadget desde foo()
? La semántica de movimientos de C ++ 11 facilita la devolución de objetos grandes. Solo escribe Gadget foo() { ... }
y solo funcionará, y funcionará rápidamente. No necesita meterse con &&
, solo devuelva las cosas por valor y el idioma a menudo podrá hacer las optimizaciones necesarias. (Incluso antes de C ++ 03, los compiladores hicieron un trabajo notablemente bueno para evitar copiar innecesariamente).
Como dijo Stroustrup en otra parte del video (parafraseando): "Sólo un científico informático insistiría en copiar un objeto y luego en destruir el original (risas de la audiencia). ¿Por qué no mover el objeto directamente a la nueva ubicación? Esto es lo que los humanos (no los informáticos) esperan ".
Cuando puede garantizar que solo se necesita una copia de un objeto, es mucho más fácil entender la vida útil del objeto. Puede elegir la política de por vida que desea, y la recolección de basura está ahí si lo desea. Pero cuando comprende los beneficios de los otros enfoques, encontrará que la recolección de basura se encuentra al final de su lista de preferencias.
Si eso no funciona para usted, puede usar unique_ptr
, o en su defecto, shared_ptr
. El C ++ 11 bien escrito es más corto, más fácil de leer y más fácil de enseñar que muchos otros idiomas cuando se trata de la administración de memoria.
Una de las razones más importantes por las que C ++ no tiene una recolección de basura integrada es que hacer que la recolección de basura funcione bien con los destructores es realmente muy difícil. Por lo que sé, nadie sabe cómo resolverlo completamente todavía. Hay un montón de problemas para tratar con:
- tiempos de vida deterministas de los objetos (el conteo de referencias le brinda esto, pero GC no. Aunque puede que no sea tan importante).
- ¿Qué sucede si un destructor arroja cuando el objeto se recolecta como basura? La mayoría de los idiomas ignoran esta excepción, ya que realmente no hay bloque catch para poder transportarla, pero probablemente esta no sea una solución aceptable para C ++.
- ¿Cómo habilitar / deshabilitarlo? Naturalmente, probablemente sea una decisión de compilación, pero el código escrito para GC vs el código escrito para NOT GC será muy diferente y probablemente incompatible. ¿Cómo se reconcilia esto?
Estos son solo algunos de los problemas que enfrentan.
Uno de los principios fundamentales detrás del lenguaje C original es que la memoria se compone de una secuencia de bytes, y el código solo debe preocuparse por lo que significan esos bytes en el momento exacto en que se utilizan. C moderna permite a los compiladores imponer restricciones adicionales, pero C incluye - y C ++ retiene - la capacidad de descomponer un puntero en una secuencia de bytes, ensamblar cualquier secuencia de bytes que contengan los mismos valores en un puntero, y luego usar ese puntero para acceder al objeto anterior.
Si bien esa capacidad puede ser útil, o incluso indispensable, en algunos tipos de aplicaciones, un lenguaje que incluya esa capacidad será muy limitado en su capacidad para admitir cualquier tipo de recolección de basura útil y confiable. Si un compilador no sabe todo lo que se ha hecho con los bits que forman un puntero, no tendrá forma de saber si la información suficiente para reconstruir el puntero podría existir en algún lugar del universo. Dado que sería posible almacenar esa información de manera que la computadora no pudiera acceder incluso si supiera de ellos (por ejemplo, los bytes que forman el puntero podrían haber sido mostrados en la pantalla el tiempo suficiente para que alguien escriba). them down on a piece of paper), it may be literally impossible for a computer to know whether a pointer could possibly be used in the future.
Una peculiaridad interesante de muchos marcos recopilados de basura es que una referencia de objeto no está definida por los patrones de bits contenidos en ella, sino por la relación entre los bits contenidos en la referencia de objeto y otra información contenida en otro lugar. En C y C ++, si el patrón de bits almacenado en un puntero identifica un objeto, ese patrón de bits identificará ese objeto hasta que el objeto se destruya explícitamente. En un sistema GC típico, un objeto puede estar representado por un patrón de bits 0x1234ABCD en un momento dado, pero el siguiente ciclo GC podría reemplazar todas las referencias a 0x1234ABCD con referencias a 0x4321BABE, con lo cual el objeto estaría representado por el último patrón. Incluso si uno tuviera que mostrar el patrón de bits asociado con una referencia de objeto y luego leerlo nuevamente desde el teclado,no habría ninguna expectativa de que el mismo patrón de bits sea utilizable para identificar el mismo objeto (o cualquier objeto)
Cuando compara C ++ con Java, puede ver inmediatamente que C ++ no fue diseñado teniendo en cuenta la recolección de basura implícita en mente, mientras que Java sí lo fue.
Tener elementos como punteros arbitrarios en C-Style y destructores deterministas no solo ralentiza el rendimiento de las implementaciones de GC, sino que también destruiría la compatibilidad con versiones anteriores para una gran cantidad de C ++: código heredado.
Además de eso, C ++ es un lenguaje que está destinado a ejecutarse como un ejecutable independiente en lugar de tener un entorno complejo de tiempo de ejecución.
En definitiva: sí, sería posible agregar la recolección de basura a C ++, pero por el bien de la continuidad es mejor no hacerlo. El costo de hacerlo sería mayor que el beneficio.
Imponer la recolección de basura es realmente un cambio de paradigma de nivel bajo a alto.
Si observa la forma en que se manejan las cadenas en un lenguaje con recolección de basura, encontrará que SOLO permiten funciones de manipulación de cadenas de alto nivel y no permite el acceso binario a las cadenas. En pocas palabras, todas las funciones de cadena comprueban primero los punteros para ver dónde está la cadena, incluso si solo está extrayendo un byte. Entonces, si está haciendo un bucle que procesa cada byte en una cadena en un lenguaje con recolección de basura, debe calcular la ubicación base más el desplazamiento de cada iteración, porque no puede saber cuándo se ha movido la cadena. Entonces tienes que pensar en montones, pilas, hilos, etc. etc.
Principalmente por dos razones:
- Porque no necesita uno (IMHO)
- Porque es bastante incompatible con RAII, que es la piedra angular de C ++
C ++ ya ofrece administración manual de memoria, asignación de pila, RAII, contenedores, punteros automáticos, punteros inteligentes ... Eso debería ser suficiente. Los recolectores de basura son para programadores perezosos que no quieren pasar 5 minutos pensando en quién debe poseer qué objetos o cuándo deben liberarse los recursos. Así no es como hacemos las cosas en C ++.
RESPUESTA CORTA: No sabemos cómo hacer la recolección de basura de manera eficiente (con una sobrecarga de tiempo y espacio menor) y correctamente todo el tiempo (en todos los casos posibles).
RESPUESTA LARGA: Al igual que C, C ++ es un lenguaje de sistemas; esto significa que se usa cuando está escribiendo el código del sistema, por ejemplo, el sistema operativo. En otras palabras, C ++ está diseñado, al igual que C, con el mejor rendimiento posible como objetivo principal. El estándar del lenguaje no agregará ninguna característica que pueda obstaculizar el objetivo de rendimiento.
Esto detiene la pregunta: ¿Por qué la recolección de basura dificulta el rendimiento? La razón principal es que, cuando se trata de la implementación, nosotros [los informáticos] no sabemos cómo hacer la recolección de basura con una sobrecarga mínima, para todos los casos. Por lo tanto, es imposible para el compilador de C ++ y el sistema de ejecución ejecutar la recolección de basura de manera eficiente todo el tiempo. Por otro lado, un programador de C ++ debe conocer su diseño / implementación y es la mejor persona para decidir cómo hacer la mejor recolección de basura.
Por último, si el control (hardware, detalles, etc.) y el rendimiento (tiempo, espacio, energía, etc.) no son las principales limitaciones, entonces C ++ no es la herramienta de escritura. Otro idioma podría servir mejor y ofrecer más gestión de tiempo de ejecución [oculta], con la sobrecarga necesaria.
Toda la charla técnica está complicando demasiado el concepto.
Si coloca GC en C ++ para toda la memoria automáticamente, entonces considere algo como un navegador web. El navegador web debe cargar un documento web completo Y ejecutar scripts web. Puede almacenar variables de script web en el árbol de documentos. En un documento GRANDE en un navegador con muchas pestañas abiertas, significa que cada vez que el GC debe realizar una recopilación completa también debe escanear todos los elementos del documento.
En la mayoría de las computadoras, esto significa que se producirán FALLAS DE PÁGINA. Entonces, la razón principal para responder a la pregunta es que ocurrirán FALLAS DE PÁGINA. Sabrás esto cuando tu PC comience a hacer mucho acceso al disco. Esto se debe a que el GC debe tocar mucha memoria para probar los punteros no válidos. Cuando tiene una aplicación de buena fe que usa mucha memoria, tener que escanear todos los objetos cada colección es un caos debido a las FALLAS DE PÁGINA. Un error de página es cuando la memoria virtual necesita ser leída nuevamente en la RAM del disco.
Entonces, la solución correcta es dividir una aplicación en las partes que necesitan GC y las partes que no lo necesitan. En el caso del ejemplo de navegador web anterior, si al árbol de documentos se le asignó malloc, pero el javascript se ejecutó con GC, cada vez que el GC se activa solo escanea una pequeña parte de la memoria y todos los elementos PAGED OUT de la memoria para el árbol de documentos no necesita volver a paginarse.
Para comprender mejor este problema, busque en la memoria virtual y cómo se implementa en las computadoras. Se trata del hecho de que 2 GB están disponibles para el programa cuando en realidad no hay tanta RAM. En las computadoras modernas con 2GB de RAM para un sistema de 32BIt no es un problema, siempre y cuando solo se esté ejecutando un programa.
Como ejemplo adicional, considere una colección completa que debe rastrear todos los objetos. Primero debes escanear todos los objetos accesibles a través de las raíces. Segundo escanee todos los objetos visibles en el paso 1. Luego escanee los destructores en espera. Luego, vuelva a todas las páginas y apague todos los objetos invisibles. Esto significa que muchas páginas pueden intercambiarse y devolverse varias veces.
Así que mi respuesta para abreviar es que la cantidad de FALLAS DE PÁGINA que se producen como resultado de tocar toda la memoria hace que no sea factible la GC completa para todos los objetos en un programa, por lo que el programador debe ver la GC como una ayuda para cosas como scripts y trabajo de base de datos, pero hacer cosas normales con la gestión manual de la memoria.
Y la otra razón muy importante, por supuesto, son las variables globales. Para que el recopilador sepa que hay un puntero de variable global en el GC, se necesitarían palabras clave específicas y, por lo tanto, el código C ++ existente no funcionaría.