Hay una diferencia importante entre los dos.
Todo lo que no está asignado se new
comporta de manera muy similar a los tipos de valor en C # (y la gente suele decir que esos objetos están asignados en la pila, que es probablemente el caso más común / obvio, pero no siempre es cierto. Más precisamente, los objetos asignados sin usar new
tienen almacenamiento automático duración
Todo asignado connew
se asigna en el montón y se devuelve un puntero al mismo, exactamente como los tipos de referencia en C #.
Todo lo asignado en la pila debe tener un tamaño constante, determinado en tiempo de compilación (el compilador debe establecer el puntero de la pila correctamente, o si el objeto es miembro de otra clase, tiene que ajustar el tamaño de esa otra clase) . Es por eso que las matrices en C # son tipos de referencia. Tienen que serlo, porque con los tipos de referencia, podemos decidir en tiempo de ejecución cuánta memoria pedir. Y lo mismo se aplica aquí. Solo las matrices con tamaño constante (un tamaño que se puede determinar en tiempo de compilación) se pueden asignar con una duración de almacenamiento automática (en la pila). Las matrices de tamaño dinámico deben asignarse en el montón, llamando new
.
(Y ahí es donde se detiene cualquier similitud con C #)
Ahora, cualquier cosa asignada en la pila tiene una duración de almacenamiento "automática" (en realidad puede declarar una variable como auto
, pero este es el valor predeterminado si no se especifica ningún otro tipo de almacenamiento, por lo que la palabra clave no se usa realmente en la práctica, pero aquí es donde viene de)
La duración del almacenamiento automático significa exactamente cómo suena, la duración de la variable se maneja automáticamente. Por el contrario, cualquier cosa asignada en el montón debe ser eliminada manualmente por usted. Aquí hay un ejemplo:
void foo() {
bar b;
bar* b2 = new bar();
}
Esta función crea tres valores que vale la pena considerar:
En la línea 1, declara una variable b
de tipo bar
en la pila (duración automática).
En la línea 2, declara un bar
puntero b2
en la pila (duración automática) y llama a nuevo, asignando unbar
objeto en el montón. (duración dinámica)
Cuando la función regrese, sucederá lo siguiente: Primero, b2
queda fuera de alcance (el orden de destrucción siempre es opuesto al orden de construcción). Pero b2
es solo un puntero, por lo que no pasa nada, la memoria que ocupa simplemente se libera. Y lo más importante, la memoria a la que apunta (la bar
instancia en el montón) NO se toca. Solo se libera el puntero, porque solo el puntero tenía una duración automática. En segundo lugar, b
queda fuera de alcance, por lo que, dado que tiene una duración automática, se llama a su destructor y se libera la memoria.
Y el bar
instancia en el montón? Probablemente todavía esté allí. Nadie se molestó en eliminarlo, por lo que hemos perdido memoria.
A partir de este ejemplo, podemos ver que cualquier cosa con duración automática tiene garantizado que se llamará a su destructor cuando salga del alcance. Eso es útil Pero cualquier cosa asignada en el almacenamiento dinámico dura tanto como lo necesitemos, y puede dimensionarse dinámicamente, como en el caso de las matrices. Eso también es útil. Podemos usar eso para administrar nuestras asignaciones de memoria. ¿Qué pasa si la clase Foo asignó algo de memoria en el montón en su constructor y eliminó esa memoria en su destructor? Entonces podríamos obtener lo mejor de ambos mundos, asignaciones de memoria seguras que se garantiza que serán liberadas nuevamente, pero sin las limitaciones de obligar a que todo esté en la pila.
Y eso es casi exactamente cómo funciona la mayoría del código C ++. Mira la biblioteca estándarstd::vector
por ejemplo. Eso normalmente se asigna en la pila, pero se puede dimensionar y cambiar de tamaño dinámicamente. Y lo hace mediante la asignación interna de memoria en el montón según sea necesario. El usuario de la clase nunca ve esto, por lo que no hay posibilidad de pérdida de memoria u olvido de limpiar lo que asignó.
Este principio se llama RAII (Adquisición de recursos es inicialización) y se puede extender a cualquier recurso que deba adquirirse y liberarse. (tomas de red, archivos, conexiones de bases de datos, bloqueos de sincronización). Todos ellos pueden adquirirse en el constructor y liberarse en el destructor, por lo que tiene la garantía de que todos los recursos que adquiera se liberarán nuevamente.
Como regla general, nunca use new / delete directamente de su código de alto nivel. Siempre envuélvala en una clase que pueda administrar la memoria por usted y que garantice que se libere nuevamente. (Sí, puede haber excepciones a esta regla. En particular, los punteros inteligentes requieren que llame new
directamente y pase el puntero a su constructor, que luego se hace cargo y asegura que delete
se llame correctamente. Pero esta sigue siendo una regla general muy importante )