Ir al contenido



Foto

Concepto: constructores y destructores


  • Por favor identifícate para responder
8 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.280 mensajes
  • LocationArgentina

Escrito 08 enero 2009 - 10:05

Constructores y Destructores

En esta nueva oportunidad hablaremos de los constructores y destructores. No, no es que existe una pandilla que construye código y otra que lo destruye.
Bueno, fueras broma, nos estamos refiriendo a los constructores y destructores que posee una clase.

Cuando uno desea crear un objeto, hace normalmente esto:



delphi
  1. Objeto := TObjecto.Create;



Y con ello ya tiene en memoria una instancia de dicha clase.
Create es el constructor y se encarga de inicializar algunos o todos los atributos del objeto una vez que se ha reservado la memoria. Luego cuando ya no es necesario el objeto, simplemente se lo libera de memoria:



delphi
  1. Objeto.Free;



El método Free envía el mensaje Destroy y éste último se encarga de liberar la memoria. Destroy es el destructor.

Un constructor y destructor no es más que otro método, con la salvedad en que en vez de definirlo como procedimiento o función se emplean las palabras "constructor" y "destructor" respectivamente. Por ejemplo:



delphi
  1. type
  2.   TPerro = class(TMamifero)
  3.   ...
  4.   public
  5.   ...
  6.   constructor Create;
  7.   destructor Destroy; override;
  8.   end;



Luego en su implementación se ve algo como esto:



delphi
  1. constructor TPerro.Create;
  2. begin
  3.   inherited Create;
  4.   // algunas inicializaciones
  5. end;
  6.  
  7. destructor TPerro.Destroy;
  8. begin
  9.   // algunas operaciones de interés y liberaciones de objetos dependientes
  10.   inherited Destroy;
  11. end;



Como puede apreciarse, a Destroy le he añadido la clásula override. Es fundamental declararla puesto que el constructor Destroy es virtual en TObject. En este ejemplo es redundante tanto el constructor como el destructor ya que el mismo constructor y Destructor de la clase TObject se encarga de la administración de memoria de los métodos simples.
Y además, se ve que intenamente se invoca al constructor y destructor de la clase TObject, al establecer la clásula inherited. Esto es necesario para que el constructor y destructor que definimos funcione apropiadamente. De éste modo nos garantizamos de que se inicializan los valores que se necesiten o se heredan desde TObject.

¿Cuando debemos declarar constructores y destructores?

Es necesario cuando entre los atributos, o internamente como alguna variable más, contamos con tipos complejos que requieren de manejo de memoria en forma explícita. Es decir cuando es nuestra responsabilidad manejar el uso de memoria. Por mencionar dos ejemplos: cuando un atributo es una clase o un array dinámico.

Por poner algo más visual. Digamos que queremos que nuestro TPerro tenga la habilidad de criar y mantener a sus crías (obviemos el tema semántico por el momento). Para tener referencia de sus crías podemos hacer uso de TObjectList. De este modo se concibe esta idea:



delphi
  1. type
  2.   TPerro = class
  3.   private
  4.     ListaCrias: TObjectList;
  5.     ...
  6.   public
  7.     ...
  8.     constructor Create;
  9.     destructor Destroy override;
  10.   end;



Como tenemos una clase definida, e internamente vamos a hacer uso de ella. Create es un método candidato (no necesariamente debe ser aquí donde se crea. Depende de diseño y propósitos) para crearla:



delphi
  1. constructor TPerro.Create;
  2. begin
  3.   inherited Create;
  4.   ...
  5.   FListaCrias := TObjectList.Create;
  6.   ...
  7. end;
  8.  
  9. destructor TPerro.Destroy;
  10. begin
  11.   ...
  12.   FListaCrrias.Clear;
  13.   FListaCrias.Free;
  14.   ...
  15.   inherited Destroy;
  16. end;



Como puede apreciarse reservamos la memoria en Create y la liberamos en Destroy.
Continuaremos hablando del tema en otro post.
  • 0

#2 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 enero 2009 - 10:56

Saludos.

Estos temas de OOP me fascinan, desde que los vi he estado tratando de comprenderlos, porque en realidad disminuye el código.

Tocando el tema, espero que en las próximas presentaciones comentes sobre Class Function y Class Procedure, que en un sentido pueden pasarse como Constructores.

Otra cosa pienso que te falto mencionar la directiva reintroduce en el constructor, agregada por Borland a partir de la versión 5 para callar al compilador.
  • 0

#3 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.280 mensajes
  • LocationArgentina

Escrito 08 enero 2009 - 11:11

Saludos.

Estos temas de OOP me fascinan, desde que los vi he estado tratando de comprenderlos, porque en realidad disminuye el código.

Tocando el tema, espero que en las próximas presentaciones comentes sobre Class Function y Class Procedure, que en un sentido pueden pasarse como Constructores.

Otra cosa pienso que te falto mencionar la directiva reintroduce en el constructor, agregada por Borland a partir de la versión 5 para callar al compilador.

Hola RolphyReyes,
Tengo pensado hacerlo, aunque no te sabría decir cuando. En la medida en que puedo sacar tiempo, y ordenar ideas, voy llevando estos tutoriales.
Por otro lado, por el tema de reintroduce, no estoy totalmente familiarizado con el concepto. Por lo poco que comprendo, se debe emplear reintroduce cuando se desea sobrecargar un método virtual de una clase base.
Sería bueno que aportaras más sobre este concepto, a modo de ilustrar a los interesados (yo incluído).

Este tutorial no termina con el anterior post. Tengo pensado continuar un poquito más al tema.

Saludos,
  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.280 mensajes
  • LocationArgentina

Escrito 08 enero 2009 - 12:23

Varios Constructores

Habrá notado que cuando uno desea crear un componente en tiempo de ejecución. el constructor le indica que espera un parámetro. ¿Porqué TPerro y otros ejemplos tratado hasta ahora no tenían que declararlo?

Porque simplemente, nuestros ejemplos están basados en la clase TObject y en ésta el constructor Create no espera parámetro alguno. En cambio la clase TComponent ofrece otro constructor monoparamétrico en el cual se debe indicar el objeto dueño del componente que estamos creando.
Por ello es que cuando uno desea crear cualquier objeto que descienda de la clase TComponent debe indicar este parámetro.

La realidad es que podemos definir más de un constructor. Cada uno con una implementación diferente, que puede recibir o no parámetros. Por ejemplo, supongamos que deseamos crear un perro, e iniciarle un nombre de entrada.

Podemos concebir a TPerro del siguiente modo:



delphi
  1. TPerro = class
  2.   private
  3.     FNombre: string;
  4.   public
  5.     constructor Create;
  6.     constructor CrearConNombre(Nombre: string);
  7.     destructor Destroy; override;
  8.     property Nombre: string read FNombre;
  9.   end;



Y el nuevo constructor podría ser como el que sigue:


delphi
  1. constructor TPerro.CrearConNombre(Nombre: string);
  2. begin
  3.   inherited Create;
  4.   FNombre := Nombre;
  5. end;



Ahora pruebe el siguiente código:


delphi
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var Perro: TPerro;
  3. begin
  4.   Perro := TPerro.CrearConNombre('Marcelo');
  5.   ShowMessage(Perro.Nombre);
  6.   FreeAndNil(Perro);
  7.  
  8.   Perro := TPerro.Create;
  9.   ShowMessage(Perro.Nombre);
  10.   FreeAndNil(Perro);
  11. end;



Deberá recibir dos cuadros de diálogo. El primero con el texto "Marcelo" y el segundo con el nombe "Fido".
De igual modo es permisible sobrecargar el constructor de éste modo:



delphi
  1. constructor Create; overload;
  2. constructor Create(Nombre: string); overload;



Las mismas reglas que se aplican a cualquier otro método son válidas para el constructor.

¿Porqué Free y no directamente Destroy?

Uno puede pensar que si Destroy es quien libera la memoria ¿porqué no usarlo de forma directa?
El motivo, Free es un medio seguro, ya que realiza un chequeo de último momento: analiza si el objeto en realidad existe.



delphi
  1. procedure TObject.Free;
  2. begin
  3.   if Self <> nil then
  4.     Destroy;
  5. end;



Es decir, que antes de liberar la memoria se comprueba de que exista algo por liberar. Supongase de que no existiera el método Free, y debemos hacer uso de Destroy. Ahora altere el último ejemplo con este:



delphi
  1. Perro := TPerro.Create('Marcelo');
  2. ShowMessage(Perro.Nombre);
  3. FreeAndNil(Perro);
  4.  
  5. Perro := TPerro.Create;
  6. ShowMessage(Perro.Nombre);
  7. FreeAndNil(Perro);
  8.  
  9. Perro.Destroy;



Al finalizar toda la rutina debería recibir una excepción EAccessViolation. ¿Porqué? Porque se está intentando eliminar algo que ya fue eliminado. Con Free no se recibe dicha violación, Free es garantía de que si el objeto existiese se lo libere, en otro caso no realiza nada.
En tiempo de diseño no vemos que se está liberando dos veces... siempre puede haber un margen al error y por ello se aconseja muchísimo Free por sobre Destroy. Este ejemplo es sencillo, y tal vez pueda omitirse el Free y valernos de Destroy en forma directa. Pero imagine un código sumamente complejo, en donde intervienen muchas clases, y muchas relaciones. La mejor forma de garantizar de que el objeto sea liberado es emplear Free. De igual forma también debe ser considerado en cuenta que una vez liberado no se haga el intento de seguir empleandolo.

Si usted puede garantizar que siempre se liberará, y que luego no habrá referencia alguna sobre el objeto. Puede hacer uso directo de Free. Más no es algo muy deseable confiarse.

Como observación, puede notar que se llama al método Destroy. Si nuestra clase implementa uno (y por tanto que redefine al de TObject) se hará uso de dicho constructor, en otro caso de invoca al de TObject. Por esto es que se aconseja mucho redefinir el destructor que ya ofrece TObject, en vez de estar declarando uno propio. Como por ejemplo:



delphi
  1. TPerro = class
  2. public
  3. ...
  4. destructor Destruir;
  5. end;



Saludos,
  • 0

#5 memofer

memofer

    Member

  • Miembros
  • PipPip
  • 32 mensajes

Escrito 12 febrero 2013 - 11:44

Gracias por tus excelente explicaciones, me ayudan a entender mas todo este conecpto de destruir y construir.
Solo quisiera saber si tienes algun tip para saber si un objeto ya fue destruido o no?
  • 0

#6 cadetill

cadetill

    Advanced Member

  • Moderadores
  • PipPipPip
  • 994 mensajes
  • LocationEspaña

Escrito 13 febrero 2013 - 01:46

Buenas,

Para saber de la existencia (o no) de un objeto tienes la función Assign



delphi
  1. if Assign(MiObjecto) then // el objeto existe
  2. else // el objeto no existe



Por supuesto también puedes hacer la negación si sólo quieres saber si no existe



delphi
  1. if not Assign(MiObjecto) then // el objeto no existe



Nos leemos

  • 0

#7 memofer

memofer

    Member

  • Miembros
  • PipPip
  • 32 mensajes

Escrito 13 febrero 2013 - 10:39

Cadetill

En mi caso, estoy usando RAD Studio XE2 y la funcion Assign(MiObjeto) no me lo identifica como valor booleano
osea que con el


delphi
  1. if Assign(MiObjeto) then
  2. ...


no me lo reconoce y verificando la sintaxis de Assign() me dice esto:

Delphi
procedure Assign(Source: TPersistent); virtual;
Description
Copies the contents of another similar object.
Call Assign to copy the properties or other attributes of one object from another. The standard form of a call to Assign is:
Destination.Assign(Source); {Delphi}

No se si tuvieras algun otro tip o consejo.
Gracias.

  • 0

#8 cadetill

cadetill

    Advanced Member

  • Moderadores
  • PipPipPip
  • 994 mensajes
  • LocationEspaña

Escrito 13 febrero 2013 - 11:40

Perdón, eso me pasa por hacer las cosas de memoria jejejeje No es Assign, sino Assigned, por lo que sería



delphi
  1. if Assigned(MiObjecto) then // el objeto existe



Nos leemos
  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.280 mensajes
  • LocationArgentina

Escrito 13 febrero 2013 - 09:10

Hola,
Lamento no haber estado antes para responderte. Estoy con pocos tiempos.
Como te han indicado, lo aconsejable es emplear Assigned. Cuya implementación no es más que un if evaluando si el objeto es nil. Es decir, algo como:



delphi
  1. result := Objeto <> nil;



Entonces el resultado será true para cuando el objeto sea distinto a nulo, y falso en caso contrario.

Saludos,

  • 0