Aprendí C # primero, y ahora estoy comenzando con C ++. Según tengo entendido, el operador new
en C ++ no es similar al de C #.
¿Puede explicar la razón de la pérdida de memoria en este código de muestra?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Respuestas:
Qué está pasando
Cuando escribe
T t;
, está creando un objeto de tipoT
con duración de almacenamiento automático . Se limpiará automáticamente cuando salga del alcance.Cuando escribe
new T()
, está creando un objeto de tipoT
con una duración de almacenamiento dinámico . No se limpiará automáticamente.Debes pasarle un puntero
delete
para limpiarlo:Sin embargo, su segundo ejemplo es peor: está desreferenciando el puntero y haciendo una copia del objeto. De esta manera, pierde el puntero al objeto creado
new
, por lo que nunca puede eliminarlo, ¡incluso si lo desea!Que deberias hacer
Debe preferir la duración del almacenamiento automático. Necesita un nuevo objeto, solo escriba:
Si necesita una duración de almacenamiento dinámica, almacene el puntero al objeto asignado en un objeto de duración de almacenamiento automático que lo elimine automáticamente.
Este es un idioma común que se conoce con el nombre poco descriptivo RAII ( La adquisición de recursos es la inicialización ). Cuando adquiere un recurso que necesita limpieza, lo coloca en un objeto de duración de almacenamiento automático para que no tenga que preocuparse por limpiarlo. Esto se aplica a cualquier recurso, ya sea memoria, archivos abiertos, conexiones de red o lo que desee.
Esto
automatic_pointer
ya existe en varias formas, lo acabo de proporcionar para dar un ejemplo. Existe una clase muy similar en la biblioteca estándar llamadastd::unique_ptr
.También hay uno antiguo (anterior a C ++ 11) llamado
auto_ptr
pero ahora está en desuso porque tiene un comportamiento de copia extraño.Y luego hay algunos ejemplos aún más inteligentes, como
std::shared_ptr
, que permiten múltiples punteros al mismo objeto y solo lo limpia cuando se destruye el último puntero.fuente
*p += 2
, como lo haría con un puntero normal. Si no regresara por referencia, no imitaría el comportamiento de un puntero normal, que es la intención aquí.Una explicación paso a paso:
Entonces, al final de esto, tiene un objeto en el montón sin puntero, por lo que es imposible eliminarlo.
La otra muestra:
es una pérdida de memoria solo si olvida
delete
la memoria asignada:En C ++ hay objetos con almacenamiento automático, aquellos creados en la pila, que se eliminan automáticamente, y objetos con almacenamiento dinámico, en el montón, con los que asigna
new
y con los que debe liberarsedelete
. (todo esto es más o menos)Piensa que deberías tener un
delete
para cada objeto asignadonew
.EDITAR
Ahora que lo pienso,
object2
no tiene que ser una pérdida de memoria.El siguiente código es solo para hacer un punto, es una mala idea, nunca me gusta un código como este:
En este caso, dado que
other
se pasa por referencia, será el objeto exacto señalado pornew B()
. Por lo tanto, obtener su dirección&other
y eliminar el puntero liberaría la memoria.Pero no puedo enfatizar esto lo suficiente, no hagas esto. Solo está aquí para hacer un punto.
fuente
Dados dos "objetos":
No ocuparán la misma ubicación en la memoria. En otras palabras,
&a != &b
Asignar el valor de uno a otro no cambiará su ubicación, pero cambiará su contenido:
Intuitivamente, los "objetos" de puntero funcionan de la misma manera:
Ahora, veamos tu ejemplo:
Esto es asignar el valor de
new A()
aobject1
. El valor es un puntero, es decirobject1 == new A()
, pero&object1 != &(new A())
. (Tenga en cuenta que este ejemplo no es un código válido, es solo para explicación)Debido a que se preserva el valor del puntero, podemos liberar la memoria a la que apunta:
delete object1;
Debido a nuestra regla, esto se comporta de la misma maneradelete (new A());
que no tiene fugas.Para su segundo ejemplo, está copiando el objeto señalado. El valor es el contenido de ese objeto, no el puntero real. Como en cualquier otro caso
&object2 != &*(new A())
,.Hemos perdido el puntero en la memoria asignada y, por lo tanto, no podemos liberarlo.
delete &object2;
Puede parecer que funcionaría, pero porque&object2 != &*(new A())
no es equivalentedelete (new A())
y, por lo tanto, no es válido.fuente
En C # y Java, usa new para crear una instancia de cualquier clase y luego no necesita preocuparse por destruirla más tarde.
C ++ también tiene una palabra clave "nuevo" que crea un objeto, pero a diferencia de Java o C #, no es la única forma de crear un objeto.
C ++ tiene dos mecanismos para crear un objeto:
Con la creación automática, crea el objeto en un entorno de ámbito: - en una función o - como miembro de una clase (o estructura).
En una función, la crearía de esta manera:
Dentro de una clase, normalmente lo crearía de esta manera:
En el primer caso, los objetos se destruyen automáticamente cuando se sale del bloque de alcance. Esto podría ser una función o un bloque de alcance dentro de una función.
En el último caso, el objeto b se destruye junto con la instancia de A en la que es miembro.
Los objetos se asignan con nuevo cuando necesita controlar la vida útil del objeto y luego se requiere eliminar para destruirlo. Con la técnica conocida como RAII, usted se encarga de eliminar el objeto en el punto en que lo crea colocándolo dentro de un objeto automático y espera a que el destructor de ese objeto automático surta efecto.
Uno de estos objetos es shared_ptr, que invocará una lógica de "borrador", pero solo cuando se destruyen todas las instancias de shared_ptr que comparten el objeto.
En general, si bien su código puede tener muchas llamadas a nuevo, debe tener llamadas limitadas para eliminar y siempre debe asegurarse de que se llamen desde destructores u objetos "eliminadores" que se colocan en punteros inteligentes.
Sus destructores tampoco deberían arrojar excepciones.
Si hace esto, tendrá pocas pérdidas de memoria.
fuente
automatic
ydynamic
. También haystatic
.Esta línea es la causa de la fuga. Vamos a separar esto un poco ...
object2 es una variable de tipo B, almacenada en dicha dirección 1 (Sí, estoy eligiendo números arbitrarios aquí). En el lado derecho, ha pedido una nueva B, o un puntero a un objeto de tipo B. El programa con gusto le da esto y le asigna su nueva B a la dirección 2 y también crea un puntero en la dirección 3. Ahora, la única forma de acceder a los datos en la dirección 2 es a través del puntero en la dirección 3. A continuación, desreferenciaron el puntero
*
para obtener los datos a los que apunta el puntero (los datos en la dirección 2). Esto crea efectivamente una copia de esos datos y los asigna al objeto2, asignado en la dirección 1. Recuerde, es una COPIA, no el original.Ahora, aquí está el problema:
¡Nunca almacenaste ese puntero en ningún lugar donde puedas usarlo! Una vez que finaliza esta asignación, el puntero (memoria en la dirección 3, que utilizó para acceder a la dirección 2) está fuera del alcance y fuera de su alcance. Ya no puede llamar a eliminar en él y, por lo tanto, no puede limpiar la memoria en la dirección2. Lo que queda es una copia de los datos de la dirección2 en la dirección1. Dos de las mismas cosas sentadas en la memoria. A uno puede acceder, al otro no (porque perdió el camino). Es por eso que esta es una pérdida de memoria.
Sugeriría que, a partir de su fondo de C #, lea mucho sobre cómo funcionan los punteros en C ++. Son un tema avanzado y pueden tomar un tiempo para comprenderlo, pero su uso será invaluable para usted.
fuente
Si lo hace más fácil, piense en la memoria de la computadora como si fuera un hotel y los programas son clientes que alquilan habitaciones cuando las necesitan.
La forma en que funciona este hotel es que usted reserva una habitación y le dice al portero cuando se va.
Si programa los libros en una habitación y se va sin avisarle al portero, el portero pensará que la habitación todavía está en uso y no permitirá que nadie más la use. En este caso hay una fuga de habitación.
Si su programa asigna memoria y no la elimina (simplemente deja de usarla), la computadora cree que la memoria todavía está en uso y no permitirá que nadie más la use. Esta es una pérdida de memoria.
Esta no es una analogía exacta, pero podría ayudar.
fuente
Al crear
object2
, está creando una copia del objeto que creó con nuevo, pero también está perdiendo el puntero (nunca asignado) (por lo que no hay forma de eliminarlo más adelante). Para evitar esto, tendrías que hacerobject2
una referencia.fuente
Bueno, crea una pérdida de memoria si en algún momento no libera la memoria que ha asignado utilizando el
new
operador al pasar un puntero a esa memoria aldelete
operador.En tus dos casos anteriores:
Aquí no está utilizando
delete
para liberar la memoria, por lo que si suobject1
puntero se sale del alcance, tendrá una pérdida de memoria, porque habrá perdido el puntero y, por lo tanto, no puede usar eldelete
operador.Y aquí
está descartando el puntero devuelto por
new B()
, por lo que nunca puede pasar ese punterodelete
para que se libere la memoria. De ahí otra pérdida de memoria.fuente
Es esta línea la que se está filtrando de inmediato:
Aquí está creando un nuevo
B
objeto en el montón, luego creando una copia en la pila. Ya no se puede acceder al que se ha asignado en el montón y, por lo tanto, a la fuga.Esta línea no tiene fugas inmediatas:
Habría una fuga si nunca
delete
Dobject1
embargo.fuente