sirven - ¿Cuándo debo usar la nueva palabra clave en C++?
palabras reservadas en c++ y su significado (11)
¿Qué método debo usar?
Esto casi nunca está determinado por sus preferencias de escritura, sino por el contexto. Si necesita mantener el objeto a través de unas pocas pilas o si es demasiado pesado para la pila, asigne en la tienda gratuita. Además, como está asignando un objeto, también es responsable de liberar la memoria. Busque el operador delete
.
Para aliviar la carga del uso de la administración de tiendas libres, las personas han inventado cosas como auto_ptr
y unique_ptr
. Le recomiendo que eche un vistazo a estos. Incluso pueden ser de ayuda para sus problemas de escritura ;-)
He estado usando C ++ por un corto tiempo, y me he estado preguntando acerca de la nueva palabra clave. Simplemente, ¿debería usarlo o no?
1) Con la nueva palabra clave ...
MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
2) Sin la nueva palabra clave ...
MyClass myClass;
myClass.MyField = "Hello world!";
Desde una perspectiva de implementación, no parecen ser tan diferentes (pero estoy seguro de que lo son) ... Sin embargo, mi lenguaje principal es C # y, por supuesto, el primer método es al que estoy acostumbrado.
La dificultad parece ser que el método 1 es más difícil de usar con las clases estándar de C ++.
¿Qué método debo usar?
Actualización 1:
Recientemente utilicé la nueva palabra clave para la memoria del montón (o tienda libre ) para una matriz grande que estaba fuera del alcance (es decir, que se devolvía desde una función). Donde antes estaba usando la pila, lo que causaba que la mitad de los elementos estuvieran corruptos fuera del alcance, cambiar al uso del montón aseguraba que los elementos estuvieran intactos. ¡Hurra!
Actualización 2:
Un amigo mío recientemente me dijo que hay una regla simple para usar la new
palabra clave; cada vez que escriba new
, escriba delete
.
Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.
Esto ayuda a evitar pérdidas de memoria, ya que siempre debe colocar la eliminación en algún lugar (es decir, cuando la corte y la pegue en un destructor o de otra manera).
¿Está pasando myClass fuera de una función, o espera que exista fuera de esa función? Como algunos otros dijeron, todo tiene que ver con el alcance cuando no se está asignando en el montón. Cuando dejas la función, desaparece (eventualmente). Uno de los errores clásicos cometidos por los principiantes es el intento de crear un objeto local de alguna clase en una función y devolverlo sin asignarlo en el montón. Recuerdo haber depurado este tipo de cosas en mis primeros días haciendo c ++.
El segundo método crea la instancia en la pila, junto con elementos como algo declarado int
y la lista de parámetros que se pasan a la función.
El primer método deja espacio para un puntero en la pila, que ha establecido en la ubicación en la memoria donde se ha asignado una nueva MyClass
en el montón o en la tienda gratuita.
El primer método también requiere que delete
lo que creas con new
, mientras que en el segundo método, la clase se destruye y se libera automáticamente cuando cae fuera de alcance (la siguiente llave de cierre, generalmente).
Hay una diferencia importante entre los dos.
Todo lo que no se asigna con 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 duración del almacenamiento automático Todo lo asignado con new
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 ser, porque con los tipos de referencia, podemos decidir en tiempo de ejecución cuánta memoria pedir. Y lo mismo se aplica aquí. Solo los arreglos con tamaño constante (un tamaño que se puede determinar en tiempo de compilación) se pueden asignar con la duración del almacenamiento automático (en la pila). Las matrices de tamaño dinámico deben asignarse en el montón, llamando a 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 lo que suena, la duración de la variable se maneja automáticamente. Por el contrario, todo lo asignado en el montón debe ser eliminado 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 puntero de bar
b2
en la pila (duración automática), y llama a nuevo, asignando un objeto de bar
en el montón. (duración dinámica)
Cuando la función regrese, sucederá lo siguiente: Primero, b2
queda fuera del alcance (el orden de destrucción siempre es opuesto al orden de construcción). Pero b2
es solo un puntero, así que no pasa nada, la memoria que ocupa se libera simplemente. Y, lo que es más importante, la memoria a la que apunta (la instancia de bar
en el montón) NO se toca. Solo se libera el puntero, porque solo el puntero tuvo duración automática. En segundo lugar, b
queda fuera del alcance, por lo que, dado que tiene una duración automática, se llama a su destructor y se libera la memoria.
¿Y la instancia de la bar
en el montón? Probablemente todavía está allí. Nadie se molestó en borrarlo, así que perdimos la memoria.
A partir de este ejemplo, podemos ver que cualquier cosa con duración automática tiene garantizado que se llame a su destructor cuando esté fuera del alcance. Eso es útil. Pero cualquier cosa asignada en el montón dura tanto como lo necesitemos, y se puede dimensionar dinámicamente, como en el caso de 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 de nuevo, pero sin las limitaciones de forzar que todo esté en la pila.
Y eso es casi exactamente cómo funciona la mayoría de los códigos C ++. Mira el estándar de la biblioteca estándar std::vector
por ejemplo. 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 perder memoria, u olvidarse de limpiar lo que asignó.
Este principio se denomina RAII (la adquisición de recursos es la inicialización) y se puede extender a cualquier recurso que deba adquirirse y liberarse. (sockets de red, archivos, conexiones de base de datos, bloqueos de sincronización). Todos ellos pueden adquirirse en el constructor y lanzarse 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 nuevo / eliminar directamente de su código de alto nivel. Envuélvalo siempre 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 llames a new
directamente y que pasen el puntero a su constructor, que luego toma el control y asegura que se delete
correctamente). Pero esta es una regla muy importante. de pulgar)
La respuesta corta es sí, la palabra clave "nueva" es increíblemente importante, ya que cuando la usas, los datos del objeto se almacenan en el montón y no en la pila, ¡lo que es más importante!
La respuesta corta es: si usted es un principiante en C ++, nunca debe usar new
o delete
. En su lugar, debe usar punteros inteligentes como std::unique_ptr
(o con menos frecuencia, std::shared_ptr
). De esa manera, no tiene que preocuparse tanto por las pérdidas de memoria. E incluso si es más avanzado, la mejor práctica generalmente sería encapsular la forma personalizada en que está usando la new
y delete
en una clase pequeña (como un puntero inteligente personalizado) que se dedica solo a los problemas del ciclo de vida de los objetos.
Por supuesto, tras bambalinas, estos punteros inteligentes siguen realizando una asignación y desasignación dinámicas, por lo que el código que los usa aún tendrá la sobrecarga de tiempo de ejecución asociada. Otras respuestas aquí han cubierto estos problemas, y cómo tomar decisiones de diseño sobre cuándo usar punteros inteligentes en lugar de simplemente crear objetos en la pila o incorporarlos como miembros directos de un objeto, lo suficientemente bien como para no repetirlos. Pero mi resumen ejecutivo sería: no use punteros inteligentes o asignación dinámica hasta que algo lo obligue a hacerlo.
La respuesta simple es sí: new () crea un objeto en el montón (con el desafortunado efecto secundario de que tiene que administrar su vida útil (mediante la llamada explícita a eliminar), mientras que la segunda forma crea un objeto en la pila en la actual alcance y ese objeto será destruido cuando salga de alcance.
Si está escribiendo en C ++, probablemente esté escribiendo para el rendimiento. Usar la tienda nueva y gratuita es mucho más lento que usar la pila (especialmente cuando se usan hilos), así que solo úselo cuando lo necesite.
Como han dicho otros, necesitas algo nuevo cuando tu objeto necesita vivir fuera de la función o el alcance del objeto, el objeto es realmente grande o cuando no sabes el tamaño de una matriz en el momento de la compilación.
Además, trate de evitar el uso de eliminar. Envuelva su nuevo en un puntero inteligente en su lugar. Deje que el puntero inteligente llame a eliminar para usted.
Hay algunos casos en los que un puntero inteligente no es inteligente. Nunca almacene std :: auto_ptr <> dentro de un contenedor STL. Se eliminará el puntero demasiado pronto debido a las operaciones de copia dentro del contenedor. Otro caso es cuando tienes un contenedor STL realmente grande de punteros a objetos. boost :: shared_ptr <> tendrá una tonelada de sobrecarga de velocidad a medida que aumenta el número de referencias hacia arriba y hacia abajo. La mejor manera de ir en ese caso es colocar el contenedor STL en otro objeto y darle a ese objeto un destructor que llamará a eliminar en cada puntero del contenedor.
Si su variable se usa solo dentro del contexto de una sola función, es mejor que use una variable de pila, es decir, la Opción 2. Como han dicho otros, no tiene que administrar la vida útil de las variables de pila: se construyen y Se destruye automáticamente. Además, la asignación / desasignación de una variable en el montón es lenta en comparación. Si se llama a su función con la frecuencia suficiente, verá una gran mejora en el rendimiento si utiliza las variables de pila frente a las variables de pila.
Dicho esto, hay un par de instancias obvias en las que las variables de pila son insuficientes.
Si la variable de pila tiene una gran huella de memoria, corre el riesgo de desbordar la pila. De forma predeterminada, el tamaño de pila de cada subproceso es 1 MB en Windows. Es poco probable que cree una variable de pila que tenga un tamaño de 1 MB, pero debe tener en cuenta que la utilización de la pila es acumulativa. Si su función llama a una función que llama a otra función que llama a otra función que ... las variables de pila en todas estas funciones ocupan espacio en la misma pila. Las funciones recursivas pueden ejecutarse rápidamente en este problema, según la profundidad de la recursión. Si esto es un problema, puede aumentar el tamaño de la pila (no recomendado) o asignar la variable en el montón utilizando el nuevo operador (recomendado).
La otra condición más probable es que su variable debe "vivir" más allá del alcance de su función. En este caso, asignaría la variable en el montón para que pueda ser alcanzada fuera del alcance de cualquier función dada.
Sin la new
palabra clave está almacenando eso en la pila de llamadas . El almacenamiento de variables excesivamente grandes en la pila conducirá a un desbordamiento de pila .
Método 1 (usando new
)
- Asigna memoria para el objeto en el almacén libre (esto suele ser lo mismo que el montón )
- Requiere que
delete
explícitamente tu objeto más tarde. (Si no lo borras, puedes crear una pérdida de memoria) - La memoria permanece asignada hasta que la
delete
. (es decir, podríasreturn
un objeto que creaste usandonew
) - El ejemplo de la pregunta perderá memoria a menos que el puntero se
delete
d; y siempre debe eliminarse , independientemente de la ruta de control que se tome, o si se lanzan excepciones.
Método 2 (no usar new
)
- Asigna memoria para el objeto en la pila (donde van todas las variables locales) Generalmente hay menos memoria disponible para la pila; Si asigna demasiados objetos, corre el riesgo de desbordamiento de pila.
- No tendrás que
delete
más tarde. - La memoria ya no está asignada cuando está fuera de alcance. (es decir, no debe
return
un puntero a un objeto en la pila)
En cuanto a cuál utilizar; elige el método que mejor se adapte a usted, dadas las restricciones anteriores.
Algunos casos fáciles:
- Si no desea preocuparse por la
delete
llamadas (y la posibilidad de que se produzcan pérdidas de memoria ), no debe usar elnew
. - Si desea devolver un puntero a su objeto desde una función, debe usar