Vous êtes sur la page 1sur 11

CONSTRCUTORES Y DESTRUCTORES EN LENGUAJE C++

Constructores
Son unos mtodos especiales que se ejecutan automticamente al crear un objeto de la clase. En su declaracin no se especifica el tipo de dato que devuelven, y poseen el mismo nombre que la clase a la que pertenecen. Al igual que otros mtodos, puede haber varios constructores sobrecargados, aunque no pueden existir constructores virtuales. Como caracterstica especial a la hora de implementar un constructor, justo despus de la declaracin de los parmetros, se encuentra lo que se llama "lista de inicializadores". Su objetivo es llamar a los constructores de los atributos que conforman el objeto a construir. Los constructores son especiales por varios motivos: Tienen el mismo nombre que la clase a la que pertenecen. No tienen tipo de retorno, y por lo tanto no retornan ningn valor. No pueden ser heredados. Por ltimo, deben ser pblicos, no tendra ningn sentido declarar un constructor como privado, ya que siempre se usan desde el exterior de la clase, ni tampoco como protegido, ya que no puede ser heredado.

Sintaxis: class <identificador de clase> { public: <identificador de clase>(<lista de parmetros>) [: <lista de constructores>] { <cdigo del constructor> } }

Ejemplo: Aadamos un constructor a nuestra clase pareja:

#include <iostream> using namespace std; class pareja { public: // Constructor pareja(int a2, int b2); // Funciones miembro de la clase "pareja" void Lee(int &a2, int &b2); void Guarda(int a2, int b2); private: // Datos miembro de la clase "pareja" int a, b;

}; pareja::pareja(int a2, int b2) { a = a2; b = b2; } void pareja::Lee(int &a2, int &b2) { a2 = a; b2 = b; } void pareja::Guarda(int a2, int b2) { a = a2; b = b2; } int main() { pareja par1(12, 32); int x, y; par1.Lee(x, y); cout << "Valor de par1.a: " << x << endl; cout << "Valor de par1.b: " << y << endl; return 0; }
Si no definimos un contructor el compilador crear uno por defecto, sin parmetros, que no har absolutamente nada. Los datos miembros del los objetos declarados en el programa contendrn basura. Si una clase posee constructor, ser llamado siempre que se declare un objeto de esa clase. Si ese constructor requiere argumentos, como en este caso, es obligatorio suministrarlos. Por ejemplo, las siguientes declaraciones son ilegales: pareja par1; pareja par1(); La primera porque el constructor de "pareja" requiere dos parmetros, y no se suministran. La segunda es ilegal por otro motivo ms complejo. Aunque existiese un constructor sin parmetros, no se debe usar esta forma para declarar el objeto, ya que el compilador lo considera como la declaracin de un prototipo de una funcin que devuelve un objeto de tipo "pareja" y no admite parmetros. Cuando se use un constructor sin parmetros para declarar un objeto no se deben escribir los parntesis. Y las siguientes declaraciones son vlidas: pareja par1(12,43); pareja par2(45,34);

Constructor por defecto

Cuando no especifiquemos un constructor para una clase, el compilador crea uno por defecto sin argumentos. Cuando se crean objetos locales, los datos miembros no se inicializaran, contendran la "basura" que hubiese en la memoria asignada al objeto. Si se trata de objetos globales, los datos miembros se inicializan a cero. Para declarar objetos usando el constructor por defecto o un constructor que hayamos declarado sin parmetros no se debe usar el parntesis: pareja par2(); Se trata de un error frecuente cuando se empiezan a usar clases, lo correcto es declarar el objeto sin usar los parntesis: pareja par2;

Inicializacin de objetos

Hay un modo especfico para inicializar los datos miembros de los objetos en los constructores, que consiste en invocar los constructores del objeto miembro antes de las llaves de la definicin del constructor. En C++ incluso las variables de tipos bsicos como int, char o float son objetos. En C++ cualquier variable (u objeto) tiene, al menos un constructor, el constructor por defecto, incluso aquellos que son de un tipo bsico. Slo los constructores de las clases admiten inicializadores. Cada inicializador consiste en el nombre de la variable miembro a inicializar, seguida de la expresin que se usar para inicializarla entre parntesis. Los inicializadores se aadirn a continuacin del parntesis cerrado que encierra a los parmetros del constructor, antes del cuerpo del constructor y separado del parntesis por dos puntos ":". Por ejemplo, en el caso anterior de la clase "pareja" tenamos este constructor: pareja::pareja(int a2, int b2) { a = a2; b = b2; } Podemos (y debemos) sustituir ese constructor por este otro: pareja::pareja(int a2, int b2) : a(a2), b(b2) {} Por supuesto, tambin pueden usarse inicializadores en lnea, dentro de la declaracin de la clase. Ciertos miembros es obligatorio inicializarlos, ya que no pueden ser asignados, por ejemplo las constantes o las referencias. Es preferible usar la inicializacin siempre que sea posible en lugar de asignaciones, ya que frecuentemente, es menos costoso y ms predecible inicializar objetos en el momento de la creacin que usar asignaciones. Veremos ms sobre este tema cuando veamos ejemplos de clases que tienen como miembros objetos de otras clases.

Sobrecarga de constructores

Los constructores son funciones, tambin pueden definirse varios constructores para cada clase, es decir, el constructor puede sobrecargarse. La nica limitacin (como en todos los casos de sobrecarga) es que no pueden declararse varios constructores con el mismo nmero y el mismo tipo de argumentos. Por ejemplo, aadiremos un constructor adicional a la clase "pareja" que simule el constructor por defecto: class pareja { public: // Constructor pareja(int a2, int b2) : a(a2), b(b2) {} pareja() : a(0), b(0) {} // Funciones miembro de la clase "pareja" void Lee(int &a2, int &b2); void Guarda(int a2, int b2); private: // Datos miembro de la clase "pareja" int a, b; }; De este modo podemos declarar objetos de la clase pareja especificando los dos argumentos o ninguno de ellos, en este ltimo caso se inicializarn los dos datos miembros con cero.

Constructor copia

Un constructor de este tipo crea un objeto a partir de otro objeto existente. Estos constructores slo tienen un argumento, que es una referencia a un objeto de su misma clase. Sintaxis de constructor copia: tipo_clase::tipo_clase(const tipo_clase &obj); Ejemplo y usaremos tambin "pareja":

class pareja { public: // Constructor pareja(int a2=0, int b2=0) : a(a2), b(b2) {} // Constructor copia: pareja(const pareja &p); // Funciones miembro de la clase "pareja" void Lee(int &a2, int &b2); void Guarda(int a2, int b2); private: // Datos miembro de la clase "pareja"

int a, b; }; // Definicin del constructor copia: pareja::pareja(const pareja &p) : a(p.a), b(p.b) {} Para crear objetos usando el constructor copia se procede como sigue: int main() { pareja par1(12, 32) pareja par2(par1); // Uso del constructor copia: par2 = par1 int x, y; par2.Lee(x, y); cout << "Valor de par2.a: " << x << endl; cout << "Valor de par2.b: " << y << endl; return 0; }
Aunque pueda parecer confuso, el constructor copia en otras circunstancias: int main() { pareja par1(12, 32) pareja par2 = par1; // Uso del constructor copia ... En este caso se usa el constructor copia porque el objeto par2 se inicializa al mismo tiempo que se declara, por lo tanto, el compilador busca un constructor que tenga como parmetro un objeto del tipo de par1, es decir, busca un constructor copia. Tanto es as que se invoca al constructor copia aunque el valor a la derecha del signo igual no sea un objeto de tipo pareja. Disponemos de un constructor con valores por defecto para los parmetros, as que intentemos hacer esto: int main() { pareja par2 = 14; // Uso del constructor copia ... Ahora el compilador intenta crear el objeto par2 usando el constructor copia sobre el objeto 14. Pero 14 no es un objeto de la clase pareja, de modo que el compilador usa el constructor de pareja con el valor 14, y despus usa el constructor copia. Tambin para cualquier clase, si no se especifica ningn constructor copia, el compilador crea uno por defecto, y su comportamiento es exactamente el mismo que el del definido en el ejemplo anterior. Para la mayora de los casos esto ser suficiente, pero en muchas ocasiones necesitaremos redefinir el constructor copia.

Entre otros tipos de constructores en C++:

1. Constructor predeterminado. Es el constructor que no recibe ningn parmetro en la funcin. Si no se definiera ningn constructor, el sistema proporcionara uno predeterminado. Es necesario para la construccin de estructuras y contenedores de la STL. 2. Constructor de copia. Es un constructor que recibe un objeto de la misma clase, y realiza una copia de los atributos del mismo. Al igual que el predeterminado, si no se define, el sistema proporciona uno. 3. Constructor de conversin. Este constructor, recibe como nico parmetro, un objeto o variable de otro tipo distinto al suyo propio. Es decir, convierte un objeto de un tipo determinado a otro objeto del tipo que estamos generando.

Destructores
Un destructor es una funcin miembro especial de una clase que se utiliza para eliminar un objeto de esa clase, liberndose la memoria que ocupa. Un destructor se distingue fcilmente porque tiene el mismo nombre que la clase a la que pertenece precedido por una tilde ~. Un destructor no es heredado, no tiene argumentos, no puede retornar un valor (incluyendo void) y no puede ser declarado const ni static, pero s puede ser declarado virtual.

Sus principales cometidos son: Liberar los recursos computacionales que el objeto de dicha clase haya adquirido en tiempo de ejecucin al expirar ste. Quitar los vnculos que pudiesen tener otros recursos u objetos con ste.

Utilizando destructores virtuales podremos destruir objetos sin conocer su tipo (ms adelante trataremos el mecanismo de las funciones virtuales). Cuando en una clase no especificamos un destructor, el compilador crea uno por omisin, pblico. Por ejemplo, el destructor para la clase CFecha es declarado como -CFecha(); Cuando se define un destructor para una clase, ste es invocado automticamente cuando sale fuera del mbito en el que el objeto es accesible, excepto cuando el objeto ha sido creado con el operador new; en este caso, el destructor tiene que ser invocado explcitamente a travs del operador delete; lgicamente, si deseamos destruir el objeto.

Como ejemplo vamos a aadir a la clase CFecha del programa anterior, m destructor para que simplemente nos muestre un mensaje de que se ha destruid un objeto. Esto es,

class CFecha

// Datos miembro de la clase CFecha prvate: int da, mes, anyo; // Funciones miembro de la clase protected: int Bisiesto() const; public: CFecha( 1nt dd = 1, int mm = 1, int aa = 1980 CFecha(const CFecha &); ~CFecha(); CFecha &operator-(const CFecha &); void AsignarFecha(); void ObtenerFecha( int *, int *, int * ) const; int FechaCorrecta() const };

// constructor copia //destructor

CFecha::CFecha() { cout "Objeto destru1do\n"; } //...


void main() { CFecha Hoy (10, 2,1997); CFecha*pOtroDia = new Cfecha(hoy); VisualizarFecha( *pOtroDia); delete pOtroDia; }

// crea e inicializa el objeto // llama al constructor copia // llama al destructor

Ejecute ahora el programa y observe los resultados. Observar que cuando finalice la ejecucin de la funcin main se visualizar el mensaje "Objeto destruido" tantas veces como objetos haya. Observe tambin, cmo para destruir un objeto creado dinmicamente hay que utilizar el operador delete. El operador delete libera la memoria asignada por new con el fin de destruir el objeto, motivo por el cual antes invoca al destructor de la clase del objeto. El cuerpo de un destructor se ejecuta antes que los destructores de los objetos miembro. En el caso que nos ocupa, cuando se destruya un objeto CFecha, se visualizar el mensaje "Objeto destruido" y a continuacin el sistema invocar al destructor int::~int(), una vez por cada uno de los datos miembro da, mes y anyo. En otras palabras, el orden de destruccin es inverso al orden de construccin. Un destructor tambin se puede llamar explcitamente utilizando su nombre completo,

objeto.nombre_clase::~nombre_clase(); pob]eto>nombre_clase::~nombre_clase();
El hecho de que se incluya el nombre de la clase a la que pertenece el destructor es debido a la tilde (~), para que no sea interpretada como el operador complemento a 1.

Existen dos tipos de destructores pueden ser pblicos o privados, segn si se declaran: Si es pblico se llama desde cualquier parte del programa para destruir el objeto. Si es privado no se permite la destruccin del objeto por el usuario.

El uso de destructores es clave en el concepto de Adquirir Recursos es Inicializar.

Al igual que los constructores, los destructores tambin tienen algunas caractersticas especiales:

Tambin tienen el mismo nombre que la clase a la que pertenecen, pero tienen el smbolo delante. No tienen tipo de retorno, y por lo tanto no retornan ningn valor. No tienen parmetros.

No pueden ser heredados. Deben ser pblicos, no tendra ningn sentido declarar un destructor como privado, ya que siempre se usan desde el exterior de la clase, ni tampoco como protegido, ya que no puede ser heredado. No pueden ser sobrecargados, lo cual es lgico, puesto que no tienen valor de retorno ni parmetros, no hay posibilidad de sobrecarga.

Cuando se define un destructor para una clase, ste es llamado automticamente cuando se abandona el mbito en el que fue definido. Esto es as salvo cuando el objeto fue creado dinmicamente con el operador new, ya que en ese caso, cuando es necesario eliminarlo, hay que hacerlo explcitamente usando el operador delete. En general, ser necesario definir un destructor cuando nuestra clase tenga datos miembro de tipo puntero, aunque esto no es una regla estricta. Aqui se muestra otro ejemplo:
#include <iostream> #include <cstring> using namespace std; class cadena { public: cadena(); // Constructor por defecto cadena(const char *c); // Constructor desde cadena c cadena(int n); // Constructor de cadena de n caracteres cadena(const cadena &); // Constructor copia ~cadena(); // Destructor void Asignar(const char *dest); char *Leer(char *c); private: char *cad; // Puntero a char: cadena de caracteres }; cadena::cadena() : cad(NULL) {} cadena::cadena(const char *c) { cad = new char[strlen(c)+1];// Reserva memoria para cadena strcpy(cad, c); // Almacena la cadena } cadena::cadena(int n) { cad = new char[n+1]; // Reserva memoria para n caracteres cad[0] = 0; // Cadena vaca } cadena::cadena(const cadena &Cad) { // Reservamos memoria para la nueva y la almacenamos cad = new char[strlen(Cad.cad)+1];

// Reserva memoria para cadena strcpy(cad, Cad.cad); } cadena::~cadena() { delete[] cad; }

// Almacena la cadena

// Libera la memoria reservada a cad

void cadena::Asignar(const char *dest) { // Eliminamos la cadena actual: delete[] cad; // Reservamos memoria para la nueva y la almacenamos cad = new char[strlen(dest)+1]; // Reserva memoria para la cadena strcpy(cad, dest); // Almacena la cadena } char *cadena::Leer(char *c) { strcpy(c, cad); return c; } int main() { cadena Cadena1("Cadena de prueba"); cadena Cadena2(Cadena1); // Cadena2 es copia de Cadena1 cadena *Cadena3; // Cadena3 es un puntero char c[256]; // Modificamos Cadena1: Cadena1.Asignar("Otra cadena diferente"); // Creamos Cadena3: Cadena3 = new cadena("Cadena de prueba n 3"); // Ver resultados cout << "Cadena 1: " << Cadena1.Leer(c) << endl; cout << "Cadena 2: " << Cadena2.Leer(c) << endl; cout << "Cadena 3: " << Cadena3->Leer(c) << endl; delete Cadena3; // Destruir Cadena3. // Cadena1 y Cadena2 se destruyen automticamente return 0; }

Observaciones: 1. Hemos implementado un constructor copia. Esto es necesario porque una simple asignacin entre los datos miembro "cad" no copiara la cadena de un objeto a otro, sino nicamente los punteros. Por ejemplo, si definimos el constructor copia como:
cadena::cadena(const cadena &Cad) {

cad = Cad.cad; }

en lugar de cmo lo hacemos en el ejemplo, lo que estaramos copiando sera el valor del puntero cad, con lo cual, ambos punteros estaran apuntando a la misma posicin de memoria. Esto es desastroso, y no simplemente porque los cambios en una cadena afectan a las dos, sino porque al abandonar el programa se intenta liberar automticamente la misma memoria dos veces. Lo que realmente pretendemos al asignar cadenas es crear una nueva cadena que sea copia de la cadena antigua. Esto es lo que hacemos con el constructor copia del ejemplo, y es lo que haremos ms adelante, y con ms elegancia, sobrecargando el operador de asignacin. La definicin del constructor copia que hemos creado en este ltimo ejemplo es la equivalente a la del constructor copia por defecto. 2. La funcin Leer, que usamos para obtener el valor de la cadena almacenada, no devuelve un puntero a la cadena, sino una copia de la cadena. Esto est de acuerdo con las recomendaciones sobre la programacin orientada a objetos, que aconsejan que los datos almacenados en una clase no sean accesibles directamente desde fuera de ella, sino nicamente a travs de las funciones creadas al efecto. Adems, el miembro cad es privado, y por lo tanto debe ser inaccesible desde fuera de la clase. Ms adelante veremos cmo se puede conseguir mantener la seguridad sin crear ms datos miembro. 3. La Cadena3 debe ser destruida implcitamente usando el operador delete, que a su vez invoca al destructor de la clase. Esto es as porque Cadena3 es un puntero, y la memoria que se usa en el objeto al que apunta no se libera automticamente al destruirse el puntero Cadena3.

Vous aimerez peut-être aussi