He estado usando C ++ por un tiempo corto, y me he estado preguntando sobre el nuevo 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 la perspectiva de la implementación, no parecen tan diferentes (pero estoy seguro de que lo son) ... Sin embargo, mi lenguaje principal es C # y, por supuesto, el primer método es a lo 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 memoria de almacenamiento dinámico (o tienda gratuita ) para una gran matriz que estaba fuera de alcance (es decir, que se devolvía de una función). Donde antes estaba usando la pila, lo que causaba que la mitad de los elementos se corrompiera fuera del alcance, el cambio al uso del montón aseguró que los elementos estuvieran intactos. ¡Hurra!
Actualización 2:
Un amigo mío me dijo recientemente que hay una regla simple para usar la new
palabra clave; cada vez que escribe 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 corta y pega en un destructor o no).
std::vector
ystd::shared_ptr
. Estos envuelven las llamadas hacianew
ydelete
para usted, por lo que es aún menos probable que pierda memoria. Pregúntese, por ejemplo: ¿siempre recuerda poner un correspondiente endelete
todas partes donde podría lanzarse una excepción? Ponerdelete
s a mano es más difícil de lo que piensas.Respuestas:
Método 1 (usando
new
)delete
su objeto más tarde. (Si no lo elimina, podría crear una pérdida de memoria)delete
lo hagas . (es decir, podríareturn
crear un objeto que haya creado usandonew
)delete
d; y siempre debe eliminarse , independientemente de qué ruta de control se tome, o si se lanzan excepciones.Método 2 (sin usar
new
)delete
más tarde.return
apuntar a un objeto en la pila)En cuanto a cuál usar; elige el método que mejor funcione para usted, dadas las restricciones anteriores.
Algunos casos fáciles:
delete
(y el potencial de causar pérdidas de memoria ), no debe usarlasnew
.new
fuente
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 usarnew
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:
Esta función crea tres valores que vale la pena considerar:
En la línea 1, declara una variable
b
de tipobar
en la pila (duración automática).En la línea 2, declara un
bar
punterob2
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). Perob2
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 (labar
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ándar
std::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 quedelete
se llame correctamente. Pero esta sigue siendo una regla general muy importante )fuente
Esto casi nunca está determinado por sus preferencias de escritura, sino por el contexto. Si necesita mantener el objeto en varias pilas o si es demasiado pesado para la pila, lo asigna en la tienda gratuita. Además, dado que está asignando un objeto, también es responsable de liberar la memoria. Buscar el
delete
operador.Para aliviar la carga del uso de la gestión de la tienda libre, la gente ha inventado cosas como
auto_ptr
yunique_ptr
. Le recomiendo que eche un vistazo a estos. Incluso podrían ser de ayuda para sus problemas de escritura ;-)fuente
Si está escribiendo en C ++, probablemente esté escribiendo para el rendimiento. Usar new y la tienda 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, necesita nuevo cuando su objeto necesita vivir fuera de la función o del alcance del objeto, el objeto es realmente grande o cuando no conoce el tamaño de una matriz en tiempo de compilación.
Además, trate de evitar el uso de eliminar. Envuelva su nuevo en un puntero inteligente en su lugar. Deje que la llamada del puntero inteligente se elimine por usted.
Hay algunos casos en los que un puntero inteligente no es inteligente. Nunca almacene std :: auto_ptr <> dentro de un contenedor STL. 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 ya que sube y baja los recuentos de referencia. La mejor manera de hacerlo en ese caso es colocar el contenedor STL en otro objeto y darle a ese objeto un destructor que invoque a delete en cada puntero en el contenedor.
fuente
La respuesta corta es: si eres un principiante en C ++, nunca deberías usar
new
odelete
ti mismo.En su lugar, debe usar punteros inteligentes como
std::unique_ptr
ystd::make_unique
(o con menos frecuencia,std::shared_ptr
ystd::make_shared
). 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 que está utilizandonew
ydelete
en una clase pequeña (como un puntero inteligente personalizado) que se dedica solo a los problemas del ciclo de vida del objeto.Por supuesto, detrás de escena, estos punteros inteligentes siguen realizando asignaciones dinámicas y desasignaciones, por lo que el código que los use aún tendría 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.
fuente
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 la pila .fuente
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 (llamando explícitamente a delete en él), mientras que la segunda forma crea un objeto en la pila en el actual alcance y ese objeto será destruido cuando salga del alcance.
fuente
Si su variable se usa solo dentro del contexto de una sola función, es mejor usar 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 destruido automáticamente Además, la asignación / desasignación de una variable en el montón es lenta en comparación. Si su función se llama con la frecuencia suficiente, verá una tremenda mejora en el rendimiento si usa variables de pila versus variables de pila.
Dicho esto, hay un par de casos obvios en los que las variables de la pila son insuficientes.
Si la variable de la pila tiene una gran huella de memoria, corre el riesgo de desbordar la pila. Por defecto, el tamaño de la pila de cada hilo es de 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 la pila en todas estas funciones ocupan espacio en la misma pila. Las funciones recursivas pueden encontrarse con este problema rápidamente, dependiendo de qué tan profunda sea la recursividad. 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 necesite "vivir" más allá del alcance de su función. En este caso, asignaría la variable en el montón para que pueda alcanzarse fuera del alcance de cualquier función dada.
fuente
¿Está pasando myClass fuera de una función, o espera que exista fuera de esa función? Como algunos otros dijeron, se trata de alcance cuando no está asignando en el montón. Cuando abandonas 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 ++.
fuente
El segundo método crea la instancia en la pila, junto con cosas 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
MyClass
se ha asignado un nuevo en el montón, o tienda libre.El primer método también requiere que crees
delete
lo que creasnew
, mientras que en el segundo método, la clase se destruye y libera automáticamente cuando cae fuera del alcance (la siguiente llave de cierre, por lo general).fuente
La respuesta corta es sí, la palabra clave "nueva" es increíblemente importante ya que cuando la usa, los datos del objeto se almacenan en el montón en lugar de en la pila, ¡lo que es más importante!
fuente