¿Por qué C ++ no tiene un recolector de basura?

No estoy haciendo esta pregunta debido a los méritos de la recolección de basura antes que nada. Mi principal razón para preguntar esto es que sí sé que Bjarne Stroustrup ha dicho que C ++ tendrá un recolector de basura en algún momento.

Dicho esto, ¿por qué no se ha agregado? Ya hay algunos recolectores de basura para C ++. ¿Es solo 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án en C ++ 11)?

Enlaces cruzados:

  • Recolectores de basura para C ++

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é el recostackdor no se puede agregar.

Se podría haber agregado la recolección implícita de basura, pero simplemente no hizo el corte. Probablemente debido no solo a las complicaciones de implementación, sino también a que 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 podría habilitarse opcionalmente fuera parte de C ++ 0x, pero había suficientes problemas técnicos que tuve que hacer con solo una especificación detallada de cómo un recostackdor se integra con el rest 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 aquí .

Visión general:

C ++ es muy poderoso y te permite hacer casi cualquier cosa. Por esta razón, no empuja automáticamente muchas cosas sobre ti que pueden afectar el rendimiento. La recolección de basura se puede implementar fácilmente con punteros inteligentes (objetos que envuelven punteros con un recuento de referencias, que se borran automáticamente cuando el recuento de referencias llega a 0).

C ++ se creó teniendo en cuenta a los competidores que no tenían recolección de basura. La eficiencia fue la principal preocupación que C ++ tuvo que rechazar las críticas en comparación con C y otros.

Hay 2 tipos de recolección de basura …

Recostackción de basura explícita:

C ++ 0x tendrá recolección de basura a través de 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 si no desea esperar C ++ 0x.

Colección implícita de basura:

Sin embargo, no tiene una 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 debería haber tenido tr1 de C ++ 0x, Bjarne Stroustrup en entrevistas anteriores declaró que tr1 no tenía tanto como le hubiera gustado.

Para agregar al debate aquí.

Existen problemas conocidos con la recolección de basura, y entenderlos ayuda a comprender por qué no hay ninguno en C ++.

1. ¿Desempeño?

La primera queja a menudo es sobre el rendimiento, pero la mayoría de las personas 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 ampliamente desplegadas:

  • Mark-and-Sweep kind
  • Tipo de recuento de referencia

Mark And Sweep es más rápido (menos impacto en el rendimiento general) pero adolece de un síndrome de “congelación del mundo”: es decir, cuando el GC entra en acción, todo lo demás se detiene hasta que el GC ha realizado su limpieza. Si desea construir un servidor que responda en unos pocos milisegundos … algunas transacciones no cumplirán con 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últiples porque necesita tener un recuento atómico. Además, existe el problema de los ciclos de referencia, por lo que se necesita un algoritmo inteligente para detectar esos ciclos y eliminarlos (generalmente también se implementa mediante un “congelamiento del mundo”, aunque con menos frecuencia). En general, a partir de hoy, este tipo (aunque normalmente es más receptivo o mejor dicho, se congela con menos frecuencia) es más lento que Mark And Sweep .

He visto un artículo de los implementadores de Eiffel que intentaban implementar un Recostackdor de Basura de Reference Counting que tendría un rendimiento global similar a Mark And Sweep sin el aspecto “Congelar el Mundo”. Se requiere un hilo separado para el GC (típico). El algoritmo fue un tanto aterrador (al final), pero el documento hizo un buen trabajo al presentar los conceptos uno a la vez y mostrar la evolución del algoritmo de la versión “simple” a la versión completa. Lectura recomendada si solo pudiera volver a poner mis manos en el archivo PDF …

2. La adquisición de recursos es la inicialización

Es un lenguaje común en C++ que ajustará la propiedad de los recursos dentro de un objeto para garantizar que se liberen adecuadamente. Se utiliza principalmente para la memoria ya que no tenemos recolección de basura, pero también es útil para muchas otras situaciones:

  • lockings (múltiples hilos, manejador de archivo, …)
  • conexiones (a una base de datos, otro servidor, …)

La idea es controlar adecuadamente la vida útil del objeto:

  • debe estar vivo todo el tiempo que lo necesite
  • se debe matar cuando hayas terminado con esto

El problema de GC es que si ayuda con lo primero y finalmente garantiza que más tarde … este “último” puede no ser suficiente. Si sueltas un candado, ¡realmente te gustaría que se libere ahora, para que no bloquee ninguna otra llamada!

Los idiomas con GC tienen dos opciones:

  • no use GC cuando la asignación de la stack es suficiente: normalmente es por problemas de rendimiento, pero en nuestro caso realmente ayuda ya que el scope define la duración
  • using constructo … pero es explícito (débil) RAII mientras que en C ++ RAII está implícito para que el usuario NO PUEDA involuntariamente cometer el error (omitiendo la palabra clave using )

3. Punteros inteligentes

Los punteros inteligentes a menudo aparecen como una bala de plata para manejar la memoria en C++ . Muchas veces he escuchado: no necesitamos GC, ya que tenemos punteros inteligentes.

Uno no podría estar más equivocado.

Los punteros inteligentes sí ayudan: auto_ptr y unique_ptr usan conceptos RAII, extremadamente útiles. Son tan simples que puedes escribirlos por ti mismo con bastante facilidad.

Sin embargo, cuando uno necesita compartir la propiedad se vuelve más difícil: puede compartir entre múltiples hilos y hay algunos problemas sutiles con el manejo del conteo. Por lo tanto, uno va naturalmente hacia shared_ptr .

Es genial, eso es lo que impulsa, 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 Reference Counting pero usted necesita implementar la detección de ciclos por su cuenta … Urg

Por supuesto que existe esta cosa de weak_ptr , pero desafortunadamente 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 Multi Thread, ¡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 tener claro la propiedad:

  • prefiere tener un único propietario en un momento dado, si es posible
  • si no, asegúrese de que su diagtwig de clase no tenga ningún ciclo relacionado con la propiedad y rompa con la aplicación sutil de weak_ptr

De hecho, sería genial tener un GC … sin embargo, no es un tema trivial. Y mientras tanto, solo tenemos que arremangarnos las mangas.

¿Que tipo? ¿Debería optimizarse para controladores integrados de lavadoras, teléfonos celulares, estaciones de trabajo o supercomputadoras?
¿Debería priorizar la capacidad de respuesta de la GUI 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 boost los indicadores inteligentes será suficiente para la mayoría de los usuarios

Editar: los recolectores de basura automáticos no son tanto un problema de rendimiento (siempre se puede comprar más servidor) es una cuestión de rendimiento predecible.
No saber cuándo el GC va a funcionar es como emplear un piloto de línea aérea narcoléptico, la mayoría de las veces son geniales, ¡pero cuando realmente necesitas capacidad de respuesta!

Una de las principales razones por las que C ++ no se ha integrado en la recolección de basura es que obtener recolección de basura para jugar bien con los destructores es muy, muy difícil. Por lo que sé, nadie sabe realmente cómo resolverlo completamente todavía. Hay muchos problemas con los que lidiar:

  • tiempos de vida determinísticos de los objetos (el recuento de referencias te da esto, pero GC no. A pesar de que puede no ser tan importante).
  • ¿Qué sucede si un destructor arroja cuando el objeto está siendo recogido basura? La mayoría de los lenguajes ignoran esta excepción, ya que en realidad no hay bloque de catch para poder transportarlo, pero probablemente esta no sea una solución aceptable para C ++.
  • ¿Cómo habilitarlo / deshabilitarlo? Naturalmente, probablemente sea una decisión en tiempo de comstackción, pero el código que se escribe para GC versus el código escrito para NOT GC va a ser muy diferente y probablemente incompatible. ¿Cómo concilias esto?

Estos son solo algunos de los problemas que enfrenta.

Aunque esta es una vieja pregunta, todavía hay un problema que no veo que nadie haya abordado en absoluto: la recolección de basura es casi imposible de especificar.

En particular, el estándar 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, virtualmente no hay comportamiento observable externamente.

La idea general de la recolección de basura es que debe hacer un bash razonable para garantizar que la asignación de memoria tenga éxito. Desafortunadamente, es esencialmente imposible garantizar que cualquier asignación de memoria tendrá é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 es (probablemente) imposible usar un colector de copia (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 contiguo para 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 podría ser posible obtener alguna garantía que diga (en esencia) que si repite exactamente el mismo patrón de asignación repetidamente, y lo logró la primera vez, continuará teniendo éxito en iteraciones posteriores, siempre que la memoria asignada se volvió 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 que 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 propuesta, lo que obtienes en el camino del comportamiento externamente observable fue una sola nota (no normativa) que decía:

[Nota: para los progtwigs recogidos basura, una implementación alojada de alta calidad debe intentar maximizar la cantidad de memoria inalcanzable que reclama. -Finalizar nota]

Al menos para mí, esto plantea una pregunta seria sobre el retorno de la inversión. Vamos a romper el código existente (nadie sabe exactamente cuánto, pero definitivamente bastante), colocamos nuevos requisitos en las implementaciones y nuevas restricciones en el código, y lo que obtenemos a cambio es posiblemente nada en absoluto?

Incluso en el mejor de los casos, lo que obtenemos son progtwigs que, basados ​​en pruebas con Java , probablemente requerirán alrededor de seis veces más memoria para ejecutarse a la misma velocidad que ahora. Peor aún, la recolección de basura formaba parte de Java desde el principio: C ++ coloca suficientes restricciones más sobre el recolector de basura que seguramente tendrá una relación costo / beneficio aún peor (incluso si vamos más allá de lo que garantiza la propuesta y suponemos que habría algún beneficio).

Resumiré la situación matemáticamente: esta es una situación compleja. Como cualquier matemático sabe, un número complejo tiene dos partes: real e imaginaria. Me parece que lo que tenemos aquí son costos que son reales, pero beneficios que son (al menos en su mayoría) imaginarios.

Si desea la recolección automática de basura, hay buenos recolectores de basura comerciales y de dominio público para C ++. Para las aplicaciones en las que la recogida de basura es adecuada, C ++ es un excelente lenguaje recogido de basura con un rendimiento que se compara favorablemente con otros lenguajes recogidos de basura. Consulte el lenguaje de progtwigción C ++ (3ª edición) para ver una discusión sobre la recolección automática de basura en C ++. Ver también, Hans-J. Sitio de Boehm para la recolección de basura C y C ++. Además, C ++ admite técnicas de progtwigción que permiten que la administración de 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 mal no recuerdo fue inventado antes de que el GC fuera el objective , y no creo que el lenguaje pudiera tener CG por varias razones (IE Compatibilidad con C hacia atrás)

Espero que esto ayude.

Stroustrup hizo algunos buenos comentarios sobre esto en la conferencia 2013 Going Native.

Simplemente salte a aproximadamente 25m50s en este video . (Recomiendo ver el video completo en realidad, pero esto se salta a la recolección de basura).

Cuando tiene un lenguaje realmente bueno que hace que sea fácil (y seguro, predecible, fácil de leer y fácil de enseñar) tratar objetos y valores de manera directa, evitando el uso (explícito) del montón, entonces ni siquiera quieres recolección de basura.

Con 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 comstackdores de C ++, creo que no se usará con mucha frecuencia. 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 no es seguro en C ++. ¡Pero también es inseguro en Java! En C ++, si la función regresa temprano, nunca se llamará a la delete . Pero si tiene una recolección completa de basura, como en Java, simplemente obtiene una sugerencia de que el objeto será destruido "en algún momento en el futuro" ( Actualización: es incluso peor que esto. Java no promete llamar al finalizador - tal vez nunca se llame). Esto no es suficiente si Gadget contiene un manejador de archivo abierto, o una conexión a una base de datos, o datos almacenados temporalmente para escribir en una base de datos en un momento posterior. Queremos que el dispositivo sea destruido tan pronto como haya terminado, a fin de liberar estos recursos lo antes posible. No quiere que su servidor de base de datos tenga problemas con miles de conexiones de bases de datos que ya no se necesitan; no sabe si su progtwig 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 obstáculos en el camino. No requiere que escriba 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 tiene un gran valor, pero los valores son más fáciles de enseñar porque no tiene esta acción de "acción a distancia" que obtiene con punteros (o referencias).

La mayoría de los objetos que crea solo se usan en la función que los creó, y tal vez pasaron como entradas a funciones secundarias. El progtwigdor no debería tener que pensar en la 'administración de la memoria' cuando devuelve objetos, o de lo contrario compartir objetos en partes muy separadas del software.

El scope y la vida son importantes. La mayoría de las veces, es más fácil si el tiempo de vida es el mismo que el scope. Es más fácil de entender y más fácil de enseñar. Cuando desee una vida útil diferente, debería ser obvio leer el código que está haciendo esto, mediante el uso de shared_ptr por ejemplo. (O devolver objetos (grandes) por valor, aprovechando move-semántica o unique_ptr .

Esto podría parecer un problema de eficiencia. ¿Qué sucede si quiero devolver un gadget de foo() ? La semántica de movimiento de C ++ 11 hace que sea más fácil devolver objetos grandes. Simplemente escriba Gadget foo() { ... } y simplemente funcionará, y funcionará rápidamente. No necesita meterse con && you, simplemente devuelva las cosas por valor y el lenguaje a menudo podrá hacer las optimizaciones necesarias. (Incluso antes de C ++ 03, los comstackdores hicieron un trabajo notablemente bueno para evitar la copia innecesaria).

Como dijo Stroustrup en otra parte del video (parafraseando): "Sólo un científico informático insistiría en copiar un objeto y luego destruir el original. (La audiencia se ríe). ¿Por qué no simplemente mover el objeto directamente a la nueva ubicación? (no científicos informáticos) esperan ".

Cuando puede garantizar que solo se necesita una copia de un objeto, es mucho más fácil comprender la vida del objeto. Puede elegir qué política de por vida desea, y la recolección de basura está allí si lo desea. Pero cuando comprenda los beneficios de los otros enfoques, encontrará que la recolección de basura se encuentra en la parte inferior de su lista de preferencias.

Si eso no funciona para usted, puede usar unique_ptr , o en su defecto, shared_ptr . Bien escrito C ++ 11 es más corto, fácil de leer y más fácil de enseñar que muchos otros lenguajes cuando se trata de la administración de la memoria.

La idea detrás de C ++ era que no pagaría ningún impacto en el rendimiento de las funciones que no utiliza. Así que agregar la recolección de basura habría significado tener algunos progtwigs ejecutados directamente en el hardware de la forma en que lo hace C y algunos dentro de algún tipo de máquina virtual en tiempo de ejecución.

Nada le impide usar algún tipo de punteros inteligentes que estén vinculados a algún mecanismo de recolección de basura de terceros. Me parece recordar que Microsoft hizo algo así con COM y no funcionó bien.

Para responder a la mayoría de las preguntas sobre “por qué” sobre C ++, lea Diseño y Evolución de C ++

Uno de los principios fundamentales detrás del lenguaje C original es que la memoria está compuesta de una secuencia de bytes, y al código solo le importa lo que esos bytes significan en el momento exacto en que se utilizan. Modern C permite a los comstackdores imponer restricciones adicionales, pero C incluye, y conserva C ++, la capacidad de descomponer un puntero en una secuencia de bytes, ensamblar cualquier secuencia de bytes que contenga los mismos valores en un puntero, y luego usar ese puntero para accede al objeto anterior.

Si bien esa capacidad puede ser útil, o incluso indispensable, en algunos tipos de aplicaciones, un lenguaje que incluya esa capacidad tendrá una capacidad muy limitada para admitir cualquier tipo de recolección de basura útil y confiable. Si un comstackdor no sabe todo lo que se ha hecho con los bits que componen 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 que esa información se almacenara de manera que la computadora no podría acceder incluso si supiera de ellos (por ejemplo, los bytes que componen el puntero podrían haberse mostrado en la pantalla el tiempo suficiente para que alguien escriba ellos abajo en un pedazo de papel), puede ser literalmente imposible que una computadora sepa si un puntero podría usarse en el futuro.

Una peculiaridad interesante de muchos frameworks recostackdos 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 del objeto y otra información mantenida 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 representarse mediante 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 sería representado por este último patrón. Incluso si se mostrara el patrón de bits asociado con una referencia de objeto y luego se lo volviera a leer desde el teclado, no habría expectativa de que el mismo patrón de bits se pueda utilizar para identificar el mismo objeto (o cualquier objeto).

Because these days, C++ doesn’t need it anymore.

The situation, for code written these days (C++17 and following the official language Coding Guidelines ) is as follows:

  • Most memory ownership-related code is in libraries (especially those providing containers).
  • Most use of code involving memory ownership follows the RAII pattern , so allocation is made on construction and deallocation on destruction, which happens when exiting the scope in which something was allocated.
  • You do not explicitly allocate or deallocate memory directly .
  • Raw pointers do not own memory (if you’ve followed the guidelines), so you can’t leak by passing them around.
  • If you’re wondering how you’re going to pass the starting addresses of sequences of values in memory – you’ll be doing that with a span ; no raw pointer needed.
  • If you really need an owning “pointer”, you use C++’ standard-library smart pointers – they can’t leak, and are quite efficient. Alternatively, you can pass ownership across scope boundaries with “owner pointers” . These are uncommon and must be used explicitly; and they allow for partial static checking against leaks.

“Oh yeah? But what about…

… if I just write code the way we’ve written C++ so far?”

Indeed, you could just disregard all of the guidelines and write leaky application code – and it will compile and run (and leak), same as always.

But it’s not a “just don’t do that” situation, where the developer is expected to be virtuous and exercise a lot of self control; it’s just not simpler to write non-conforming code, nor is it faster to write, nor is it better-performing. Gradually it will also become more difficult to write, as you would face an increasing “impedance mismatch” with what conforming code provides and expects.

… if I reintrepret_cast ? Or do pointer arithmetic? Or other such hacks?”

Indeed, if you put your mind to it, you can write code that messes things up despite playing nice with the guidelines. Pero:

  1. You would do this rarely (in terms of places in the code, not necessarily in terms of fraction of execution time)
  2. You would only do this intentionally, not accidentally
  3. Doing so will stand out in a codebase conforming to the guidelines
  4. It’s the kind of code in which you would bypass the GC in another language anyway

… library development?”

If you’re a C++ library developer then you do write unsafe code involving raw pointers, and you are required to code carefully and responsibly – but these are self-contained pieces of code written by experts.

so, bottom line: There’s really no motivation to collect garbage generally, as you all but make sure not to produce garbage. GC is on the way to becoming a non-problem with C++.

That is not to say GC isn’t an interesting problem for certain specific applications, when you want to employ custom allocation and de-allocations strategies. For those you would want custom allocation and de-allocation, not a language-level GC.

All the technical talking is overcomplicating the concept.

If you put GC into C++ for all the memory automatically then consider something like a web browser. The web browser must load a full web document AND run web scripts. You can store web script variables in the document tree. In a BIG document in a browser with lots of tabs open, it means that every time the GC must do a full collection it must also scan all the document elements.

On most computers this means that PAGE FAULTS will occur. So the main reason, to answer the question is that PAGE FAULTS will occur. You will know this as when your PC starts making lots of disk access. This is because the GC must touch lots of memory in order to prove invalid pointers. When you have a bona fide application using lots of memory, having to scan all objects every collection is havoc because of the PAGE FAULTS. A page fault is when virtual memory needs to get read back into RAM from disk.

So the correct solution is to divide an application into the parts that need GC and the parts that do not. In the case of the web browser example above, if the document tree was allocated with malloc, but the javascript ran with GC, then every time the GC kicks in it only scans a small portion of memory and all PAGED OUT elements of the memory for the document tree does not need to get paged back in.

To further understand this problem, look up on virtual memory and how it is implemented in computers. It is all about the fact that 2GB is available to the program when there is not really that much RAM. On modern computers with 2GB RAM for a 32BIt system it is not such a problem provided only one program is running.

As an additional example, consider a full collection that must trace all objects. First you must scan all objects reachable via roots. Second scan all the objects visible in step 1. Then scan waiting destructors. Then go to all the pages again and switch off all invisible objects. This means that many pages might get swapped out and back in multiple times.

So my answer to bring it short is that the number of PAGE FAULTS which occur as a result of touching all the memory causes full GC for all objects in a program to be unfeasible and so the programmer must view GC as an aid for things like scripts and database work, but do normal things with manual memory management.

And the other very important reason of course is global variables. In order for the collector to know that a global variable pointer is in the GC it would require specific keywords, and thus existing C++ code would not work.

SHORT ANSWER: We don’t know how to do garbage collection efficiently (with minor time and space overhead) and correctly all the time (in all possible cases).

LONG ANSWER: Just like C, C++ is a systems language; this means it is used when you are writing system code, eg, operating system. In other words, C++ is designed, just like C, with best possible performance as the main target. The language’ standard will not add any feature that might hinder the performance objective.

This pauses the question: Why garbage collection hinders performance? The main reason is that, when it comes to implementation, we [computer scientists] do not know how to do garbage collection with minimal overhead, for all cases. Hence it’s impossible to the C++ compiler and runtime system to perform garbage collection efficiently all the time. On the other hand, a C++ programmer, should know his design/implementation and he’s the best person to decide how to best do the garbage collection.

Last, if control (hardware, details, etc.) and performance (time, space, power, etc.) are not the main constraints, then C++ is not the write tool. Other language might serve better and offer more [hidden] runtime management, with the necessary overhead.

When you compare C++ with Java, you can see immediately that C++ was not designed with implicit Garbage Collection in mind, while Java was.

Having things like arbitrary pointers in C-Style and deterministic destructors does not only slow down the performance of GC-implementations, it would also destroy backward compatibility for a large amount of C++-legacy-code.

In addition to that, C++ is a language that is intended to run as standalone executable instead of having a complex run-time environment.

All in all: Yes it would be possible to add Garbage Collection to C++, but for the sake of continuity it is better not to do so. The cost of doing so would be greater than the benefit.

Mainly for two reasons:

  1. Because it doesn’t need one (IMHO)
  2. Because it’s pretty much incompatible with RAII, which is the cornerstone of C++

C++ already offers manual memory management, stack allocation, RAII, containers, automatic pointers, smart pointers… That should be enough. Garbage collectors are for lazy programmers who don’t want to spend 5 minutes thinking about who should own which objects or when should resources be freed. That’s not how we do things in C++.

Imposing garbage collection is really a low level to high level paradigm shift.

If you look at the way strings are handled in a language with garbage collection, you will find they ONLY allow high level string manipulation functions and do not allow binary access to the strings. Simply put, all string functions first check the pointers to see where the string is, even if you are only drawing out a byte. So if you are doing a loop that processes each byte in a string in a language with garbage collection, it must compute the base location plus offset for each iteration, because it cannot know when the string has moved. Then you have to think about heaps, stacks, threads, etc etc.