3

Una de las características fundamentales de la agregación según varios autores es la diferencia en el ciclo de vida del objeto agregado y el objeto que lo agrega. Ahora bien, encuentro distintas implementaciones en C++. En ninguna de las implementaciones se ve claramente que la clase agregada siga viva luego de la destrucción de la clase agregadora.

En el siguiente código cree una clase llamada agregación que contiene como miembro un puntero a un entero. En el main() le reserve memoria a dicho entero, le hice que el miembro de mi clase apunte a esa dirección. Al hacer un delete, continuo pudiendo acceder al objeto.

#include <iostream>
#include "Agregacion.h"
using namespace std;
class Agregacion
{
    public:
        Agregacion();

        void setNumero(int);
        int getNumero();

        virtual ~Agregacion();

    protected:

    private:

        int *numero;
}
int main()
{
    int* numero_main;
    numero_main = new int;
    *numero_main = 10;
    Agregacion* num;
    num = new Agregacion;
    num->setNumero(numero_main);
    cout << "Imprimiendo num.getNumero(): ";
    cout << num->getNumero() << endl;
    cout << "Imprimiendo numero_main: ";
    cout << *numero_main << endl;
    delete num;
    cout << "Imprimiendo num.getNumero(): ";
    cout << num->getNumero() << endl;
    cout << "Imprimiendo numero_main: ";
    cout << *numero_main << endl;
    return 0;
}

y los metodos basicos:

Agregacion::Agregacion()
{
    //ctor
}

Agregacion::~Agregacion()
{
    //dtor
}

void Agregacion::setNumero(int* a)
{
    numero = a;
}

int Agregacion::getNumero()
{
    return *numero;
}

Por que sucede esto? Cual seria la correcta implementacion para demostrar la diferencia entre los ciclos de vida? Muchas gracias.

2 Answers2

2

Al hacer un delete, continuo pudiendo acceder al objeto. […] ¿Por qué sucede esto?

Suceden dos cosas, en primer lugar tu programa causa un comportamiento indefinido y en segundo lugar tu programa no es quien gestiona la memoria, es el sistema operativo quien lo hace.


El estándar de C++ dice lo siguiente acerca del ciclo de vida de los objetos (traducción y resaltado míos):

6.8 Ciclo de vida del objeto.

  1. De manera similar, antes de que el ciclo de vida de un objeto empiece pero después de que el almacenamiento que el objeto ocupará haya sido alojado o, después de que el ciclo de vida de un objeto haya finalizado y antes de que el almacenamiento que el objeto ocupó sea reutilizado o liberado, cualquier glvalue que se refiera al objeto original puede ser usado de forma limitada. […] El programa tiene comportamiento indefinido si:
    1. el glvalue se usa para acceder al objeto, o
    2. el glvalue se usa para llamar a una función miembro del objeto no estática, o
    3. el glvalue está referenciado desde una clase base virtual, o
    4. el glvalue se usa como operando de dynamic_cast u operando de typeid.

En tu programa estás accediendo a un objeto cuya vida ha finalizado, esto es comportamiento indefinido con lo que no puedes tener garantías acerca del comportamiento del programa… podrías obtener un resultado esperado, o uno inesperado, o podría no compilar, o podrías tener un error en tiempo de ejecución… el comportamiento podría depender de la plataforma, del compilador, de su versión o incluso de la optimización usada.

Lo que seguramente esté pasando es que el sistema operativo no ha reutilizado la memoria ocupada por ese entero y por eso conserva el valor anterior. Una manera de entenderlo es mediante una analogía usada en otra respuesta:

Imagina que te vas de viaje, te alojas en un bonito hotel, antes de dormir lees un rato un libro y cuando te vence el sueño lo dejas en la mesilla de noche. Al día siguiente haces tus maletas y vas dirección a la estación de tu medio de transporte favorito ¡pero olvidas el libro!. ¡No pasa nada! ¡Aún estás a tiempo de volver y recuperarlo! Corres raudo al hotel, vuelves a tu habitación y encuentras el libro ahí ¡menos mal!

Pero ¡un momento! ¿No se supone que se limpian las habitaciones cuando el huésped las abandona? ¿Cómo es posible que el libro siga ahí cuando la habitación debería haberse limpiado?.

Pues no, el personal del hotel puede hacerse cargo de la habitación que estuviste usando cuando a ellos les venga bien; y esto no tiene por que ser después de tu checkout si no en cualquier otro momento.

Igual sucede con el SO y la gestión de memoria: el SO puede hacerse cargo de la memoria que estuviste usando cuando le venga bien; y esto no tiene por que ser después de tu delete[] si no en cualquier otro momento. Es más, el SO puede decidir marcar la memoria como disponible pero sin modificar su contenido; ¡de esta manera los datos antiguos estarían presentes aún después del borrado!.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
0

En C++, la llamada a delete solo implica 2 cosas:

  1. Se llama al destructor del objeto, si lo tiene.
  2. El mecanismo de gestión de memoria marcará la zona ocupada por el objeto como disponible.

¡ Cuidado ! También se especifica que acceder a cualquier atributo de un objeto, después de realizar un delete, es un comportamiento indefinido. Es decir, no se indica que ha de pasar, lo cual depende del compilador, la plataforma, la fase de la luna, las mareas, ...

En ningún momento se indica que la memoria va a cambiar de estado, aunque lo normal es que quede tal y como lo dejó el constructor (recordemos, no está especificado que tenga que pasar así).

Si quieres garantizar que realmente la destrucción de tu objeto imposibilite el acceso a sus miembros, te basta con realizar alguna cosilla en el destructor:

Agregacion::~Agregacion( ) {
  numero = nullptr;
}

Lo normal sería hacer un delete sobre ese puntero ... pero volveríamos a quedar en el mismo caso, no se garantiza que ni el puntero ni la dirección a la que apunta sean modificados.

Después de ese pequeño cambio, verás que intentar acceder a numero dentro de tu clase será infructuoso.

Trauma
  • 25,297
  • 4
  • 37
  • 60