No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Ciclo de Vida de un Objeto en C++: Constructores y Destructores

9/16
Recursos

¿Qué es el ciclo de vida de un objeto en C++?

En el mundo de la programación, entender el ciclo de vida de un objeto es esencial para manejar correctamente la memoria y evitar errores. En C++, un objeto es una entidad que ocupa un espacio en la memoria y pasa por una serie de estados desde su creación hasta su destrucción. Estos estados son gestionados mediante constructores y destructores, que determinan cómo se inicializan y descomponen los objetos. A diferencia de otros lenguajes, como Java, C++ no cuenta con un recolector de basura, por lo que es crucial entender estos conceptos para evitar fugas de memoria.

¿Cómo se reserva el espacio en memoria?

  • Asignación de memoria: Cuando creamos un objeto en C++, lo primero que sucede es que se reserva un espacio en memoria. Esto se realiza a través de la palabra reservada new, que asigna memoria dinámica a un puntero que puede almacenar el objeto.

  • Constructor: El siguiente paso en el ciclo de vida de un objeto es su inicialización mediante el constructor. Éste es una función que se llama automáticamente cuando se crea un objeto y tiene el mismo nombre que la clase. No devuelve un valor y se utiliza para inicializar los atributos del objeto. Por ejemplo, en una clase Persona, podríamos definir que el nombre y la edad se inicialicen en el constructor.

class Persona {
public:
    Persona(std::string n = "Diana", int e = 26) : nombre(n), edad(e) {}
private:
    std::string nombre;
    int edad;
};

En este ejemplo, al crear un objeto Persona, si no se especifican argumentos, por defecto tendrá el nombre "Diana" y la edad 26.

¿Qué sucede durante el período de actividad?

  • Período de actividad: Una vez inicializado, el objeto entra en un estado donde permanece en memoria por el tiempo necesario para cumplir con su propósito en el programa. Durante este tiempo, podemos acceder a sus atributos y métodos para realizar diversas operaciones.

¿Cómo se libera el espacio en memoria?

  • Destructor: Cuando un objeto ya no es necesario, su espacio en memoria debe liberarse. Aquí entra en acción el destructor, que es una función que se llama automáticamente cuando un objeto se destruye. Los destructores en C++ se identifican por compartir el nombre de la clase, precedido de una tilde (~).

  • Eliminar memoria manualmente: En C++, el programador es responsable de liberar la memoria dinámica de un objeto usando la palabra clave delete, algo que no sucede automáticamente como en otros lenguajes de programación.

~Persona() {
    // Código para liberar recursos, si es necesario
}

La eliminación manual se lleva a cabo de la siguiente manera:

Persona* p2 = new Persona("Jimena", 30);
// Operaciones con p2
delete p2;  // Aquí se libera la memoria

En este fragmento de código, antes de que termine el programa o cuando ya no se necesita p2, se utiliza delete para liberar la memoria que había sido reservada con new. Si olvidamos realizar esta operación, podríamos enfrentar fugas de memoria.

¿Qué problemas podrían surgir sin un adecuado manejo de memoria?

  • Errores de segmentación: Intentar acceder a memoria liberada genera errores de segmentación, lo que ocurre si tratamos de interactuar con un objeto después de haberlo destruido.

  • Fugas de memoria: No liberar manualmente la memoria dinámica reservada puede saturar la memoria disponible, especialmente en aplicaciones de gran escala, afectando el rendimiento del software.

En conclusión, dominar el ciclo de vida de un objeto en C++ no solo optimiza el uso de la memoria, sino que evita los errores que pueden surgir de un mal manejo de los recursos del sistema. ¡Continúa explorando y aprendiendo para perfeccionar tus habilidades en la programación orientada a objetos!

Aportes 12

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

~ <-- Se llama virgulilla y la puedes hacer con : alt + 126

En el constructor, si yo coloco

Persona(string nombre, int edad) {
      nombre = nombre;
      edad = edad;
    }

Me sale esto:

.
En cambio, si cambio un poco el constructor

Persona(string name, int age) {
      nombre = name;
      edad = age;
    }

Me sale esto:

.
Y si le aplico a this en el constructor

Persona(string nombre, int edad) {
      this->nombre = nombre;
      this->edad = edad;
    }

Me sale esto:

.
En el primer escenario, el nombre de la variable “nombre” es diferente a “name”, y por eso te guarda los datos
.
En el segundo escenario, como ambas son iguales, como que no te capta los datos y por ello no te los guarda (me gustaria saber más acerca de esto, lo que mencione es una deduccion
.
En el tercer escenario, con this accede a las variables directa del objeto y te guarda los datos.
.
Entiendo el por qué del primer y tercer escenario. Pero el segundo Como “wtf? Why? 😦”

El destructor es un método de la clase que se usa para destruir objetos del tipo de la clase, no tiene parámetros de entrada ni valor de retorno. Para declarar un destructor se utiliza el caracter virgulilla (~) seguido del nombre la clase, es decir, el destructor también se denomina del mismo modo que la clase. Los destructores se ejecutan automáticamente justo cuando un objeto alcanza el límite de su tiempo de vida.

Ese tiempo de vida está definido por el contexto (scope) donde se ha declarado el objeto. Un contexto (scope) está delimitado en C++ por las llaves { y }.

Los destructores son especialmente útiles para destruir objetos de almacenamiento dinámico, es decir, aquellos para los que se reserva memoria con ayuda de un apuntador y el operador new.

Les dejo el código

#include <iostream>
#include <string>

using namespace std;

class Person {
public:
  string name;
  int age;
  Person(string n, int a) {
    name = n;
    age = a;
  }
  ~Person() { cout << "Destruction" << endl; }
  void say_hi() { cout << name << endl; }
};

int main() {
  Person p = Person("Zero", 14);
  p.say_hi();
}

Ciclo de vida de un objeto:

  1. Se reserva un espacio en memoria.
  2. Se invoca el constructor. (Función que inicializa todos los valores necesarios dentro del objeto. Los constructores no deben retornar un tipo y deben de tener el mismo nombre que la clase).
  3. Periodo de actividad (El objeto existe en memoria durante el tiempo necesario hasta que ya no lo utilicemos).
  4. Se invoca al destructor. El destructor viene por defecto en los compiladores, como una función vacía que libera toda la memoria que se reservo. Sin embargo, cuando usamos punteros es bueno hacer nosotros el constructor o utilizar delete para limpiar la memoria dinamica manualmente porque C++ no tiene un recolector de basura como otros lenguajes de Programación.
  5. Se libera el espacio de memoria y el compilador asume que esa memoria esta desocupada.

Liberar los recursos en el destructor (delete):
Esto es lo que Bjarne Stroustrup definió como RAII (Resource Adquisition Is Initialization) por lo que cada clase es responsable de adquirir recursos y liberarlos para evitar los memory leaks.
En lenguajes como Java no hace falta liberar la memoria en un destructor porque el Garbage Collector se encarga de liberarla.
C++ no tiene GC por lo que es mas eficiente, pero a la vez mas peligroso si no se siguen estas prácticas.

En nuestro destructor liberamos la memoria que reservamos dinámicamente, es decir punteros.

No se preocupen, Diana solo se fue de la sala de chat. 🏃🏻‍♀️

Por ejemplo, en el siguiente código libero el espacio del nombre en el destructor (porque lo implemente como un puntero) 😃:

#include <iostream>
#include <string>

using namespace std;

// Declaracion de la clase Persona
class Persona
{
public:
    string *nombre;

    Persona(string *nombre);
    ~Persona();

    void saludar();
};

// Constructor
Persona::Persona(string *nombre)
{
    this->nombre = nombre;
}

// Destructor
Persona::~Persona()
{
    cout << *nombre << " se ha ido 😢" << endl;
    delete nombre;
}

// Implementacion de saludar
void Persona::saludar()
{
    cout << "Hola soy " << *nombre << " 👋!" << endl;
}

int main()
{
    string *puntero_al_nombre = new string("Diana");
    Persona *p = new Persona(puntero_al_nombre);
    p->saludar();
    delete p;
    cout << "Fin del programa" << endl;
}

Un aporte que es muy bueno ¿Alguien se acuerda del clrscr (limpiar pantalla en algunos lenguajes), para este caso use

system("clear");

Tiene el mismo efecto, úsenlo en los programas les va ayudar

Este el mi codigo de la clase :

#include <iostream>
#include <string>

using namespace std;

class Npc {
    public:
        string name;
        int age;
        int job = 0;
        Npc(string name_input,int age_input){
            name=name_input;
            age = age_input;
        }
        void interaction(){
            string ocupations[]={"Desempleado","Profesor","Ingeniero","Medico","Abogado","Programador","Arquitecto","Veterinario","Psicologo","Enfermero","Diseñador","Contador","Cocinero","Electricista","Periodista","Actor","Músico","Escritor","Policía","Bombero","Astrónomo"} ;
            if (age<18&&job==0)
                cout << "Actualmete soy estudiante"<<endl;
            else    
                cout << "Actualmete trabajo como "<< ocupations[job]<<endl;
            delete this;
        }
        void Say_hello(){
            cout << "Hola mi nombre es "<<name<<endl;
            interaction();
        }

};

int main (){
    Npc *carlos=new Npc ("Carlos de la O",42);
    /*
    carlos->name="Carlos de la O";
    carlos->age=42;
    */
    carlos->job=12;
    carlos->Say_hello();
    Npc *juan=new Npc("Carlos Pedocles Poncho",10);
    juan->Say_hello(); 

}

Codigo aplicando los atributos Private y Public en el mismo ejemplo:

#include<iostream>
#include<string>

using namespace std;

class cls_persona {
    private:
        //Propiedades
        string clave;
        int id;

    public: 
        //Propiedades
        string nombre;
        int edad;

        //Constructor        
        cls_persona(
            string vNombre,
            int vEdad,
            int vId
        ): 
            clave("EDS$#$.*#$ETERT"),
            id(vId)
        {
            nombre = vNombre;
            edad = vEdad;
        }

        //Destructor
        ~cls_persona(){
            cout << "Destructor" <<endl;
        }

        //Metodos
        void fun_saludar(){
            cout << "Mi edad es: " << edad << endl;
            cout << "Mi ID es: " << id << endl;
            cout << "Mi Clave es: " << clave << endl;
        }
};

int main() {

    cls_persona per = cls_persona("Jair Rojas",38,172058);

    cout << "Mi nombre es:" << per.nombre << endl;
    per.fun_saludar(); cout << endl;


    cls_persona *per2 = new cls_persona("Laura Skarlet",36,22541);

    //Llamada al destructor() ~
    //delete per2;
    
    cout << "Mi nombre es:" << per2->nombre << endl;
    per2->fun_saludar(); cout << endl;
}

El constructor, si existe, se llama cuando se crea el objeto.
El constructor solo puede ser llamado una sola vez en la vida del objeto.

El destructor, si existe, se llama cuando se borra el objeto.
El destructor puede ser llamado varias veces en la vida del objeto. (para que sirve?..)

#include <iostream>
#include <string>

using namespace std;

class Persona
{
    public: 
        string nombre;
        int edad;

        //constructor 
        Persona(string n, int e)
        {
            nombre = n;
            edad = e;
            cout << "El constructor  fue invocado en persona " << nombre << endl;
        }
        
        // Destructor
        ~Persona()
        {
            cout << "El destructor fue invocado en persona " << nombre << endl;
        }

        void saludar()
        {
            cout << "Hola, mi nombre es: " << nombre << " y tengo: " << edad  << " años" << endl;
        }
};

int main()
{
    Persona p1 = Persona("Maria", 25);
    cout << "nombre: " << p1.nombre << endl;
    cout << "edad:   " << p1.edad << endl;
    p1.saludar();
    cout << endl;

    Persona *p2  = new Persona("Pedro", 38);
    cout << "nombre: " << p2->nombre << endl;
    cout << "edad:   " << p2->edad << endl;
    p2->saludar();
    cout << endl;
    
    delete p2;
   
    p2 = new Persona("Miguel", 41);
    cout << "nombre: " << p2->nombre << endl;
    cout << "edad:   " << p2->edad << endl;
    p2->saludar();
    cout << endl;
    
    Persona *p3;
    p3 = new Persona("Angela",34);
    cout << "nombre: " << p3->nombre << endl;
    cout << "edad:   " << p3->edad << endl;
    p3->saludar();
    cout << endl;

    p1.~Persona();
    p2->~Persona();
    cout << endl;

    cout << "aqui se borra la variable dinamica p2" << endl;
    delete p2;

    cout << "aqui acaban las lineas del programa......" << endl; 
    return 0;
}

Ciclo de vida de un objeto:

  1. Se reserva el espacio en memoria.
  2. Se invoca el constructor.
  3. Período de actividad.
  4. Se invoca el destructor.
  5. Se libera el espacio en memoria.