Академический Документы
Профессиональный Документы
Культура Документы
Los objetos automticos son creados cada vez que el proceso encuentra una
declaracin y destruidos cuando la ejecucin sale del bloque en que son declarados (
4.1.5).
Los objetos persistentes son creados con el operador new y destruidos con delete (
4.1.5)
Un miembro no esttico de un objeto es creado y destruido cuando el objeto del que es
miembro es a su vez creado y destruido.
Un objeto esttico local es creado la primera vez que se encuentra la declaracin durante
la ejecucin del programa, y es destruido cuando el programa finaliza (
4.1.5).
Un objeto esttico global es creado al principio del programa, durante la ejecucin del
mdulo inicial (
1.5) y destruido cuando el programa finaliza.
Un objeto temporal puede ser creado como parte de la evaluacin de una expresin y
destruido al final de la evaluacin misma.
Las condiciones enunciadas de destruccin de objetos son las que podramos considerar
"normales"; en el sentido que ocurren durante la ejecucin normal del programa. Pero existen
otras causas de destruccin de objetos que acontecen durante el proceso de limpieza de pila
("Stack unwinding"
1.6) que forma parte del mecanismo C++ de excepciones.
2.2 Su declaracin no especifica ningn valor devuelto (ni siquiera void). En este sentido,
constructores y destructores son un caso especialsimo de funciones!!
Nota: el hecho que no puedan devolver ningn valor, hace que, por lo general, no existan
procedimientos sencillos o elegantes para tratar los errores que pudieran producirse durante la
fase de creacin del objeto.
2.3 Estos miembros no pueden ser heredados, aunque una clase derivada puede invocar a los
constructores y destructores de su clase-base si son accesibles. Es decir: si no han sido
declarados privados o protegidos (
4.11.2d1).
2.4 Un constructor no puede ser friend (
4.11.2a1) de ninguna otra clase. Tampoco pueden
ser declarados virtual (
4.11.2d2), static,const o volatile.
Nota: aunque el lenguaje no contempla la posibilidad de declarar constructores virtuales, s
ofrece soporte para algunas tcnicas que permiten simular este comportamiento (
4.13.5).
2.5 No pueden obtenerse sus direcciones, por lo que no pueden definirse punteros a este tipo de
funciones miembro. La sentencia del ejemplo es ilegal
int main (void) {
...
void* ptr = base::base;
// ilegal
...
}
2.6 Recuerde que, como en el resto de las funciones miembro no estticas, el primer argumento
(oculto) de constructores y destructores es el puntero this (
4.11.6), a travs del cual la funcin
sabe sobre que objeto debe actuar para inicializarlo o destruirlo.
2.7 Tenga en cuenta que un objeto que tenga constructor o destructor no puede ser utilizado
como miembro de una unin (
4.6).
//
//
//
//
//
//
Recordar que cuando se trata de iniciar o destruir objetos de tipos definidos por el usuario
(clases), los operadores new y delete pueden realizar invocaciones implcitas a los constructores y
destructores de tales clases. A su vez, constructores y destructores pueden realizar invocaciones
explcitas a los operadores new (
4.9.20) y delete (
4.9.21) si se requiere espacio persistente
para algn miembro del objeto (ver ejemplo
4.11.2d1).
// objeto automtico
C* cpt = new C;
// objeto persistente (annimo) +
objeto cpt automtico
delete cpt;
// destruccin explcita del objeto annimo
}
// destruccin implcita de c1 y cpt
3.2 Si en una clase X no se ha definido ningn constructor para aceptar un tipo de argumento
particular, el compilador no se realizar ningn intento para encontrar otro constructor, o alguna
conversin para convertir un valor asignado a un tipo aceptable para algn constructor de dicha
clase. Esta regla se aplica solo a constructores con un parmetro y sin iniciadores, que utilice la
sintaxis de asignacin =. Por ejemplo:
class X {
...
X(int);
};
class Y {
...
Y(X);
};
Y a = 1;
// constructor
// constructor
// ilegal: No es transformado a Y(X(1))
3.3 Cualquiera que sea el mtodo de invocacin del constructor (implcito o explcito) para crear
un objeto, si la clase contiene miembros abstractos ADTs (
2.2), el constructor invoca a su vez
los constructores de estos miembros. Como el constructor de los tipos escalares reserva espacio
en memoria, pero no realiza ningn tipo de inicializacin concreta, los miembros de tipo escalar
quedan sin una correcta inicializacin a menos que esta sea proporcionada explcitamente por el
programador.
Como consecuencia de la regla anterior, en el diseo de constructores no es generalmente
necesario preocuparse de la iniciacin de los miembros abstractos, ya que sus constructores sern
invocados automticamente y (suponemos) han sido correctamente establecidos al definir sus
clases. En cambio, dado que los tipos escalares (tipos simples preconstruidos en el lenguaje) no
reciben automticamente una correcta iniciacin, es probable que sus contenidos iniciales (basura)
puedan ocasionar problemas, por lo que es generalmente necesario proporcionarles una correcta
inicializacin en el constructor.
Nota: algunos autores sostienen que en estos casos, la iniciacin de los miembros escalares
debe realizarse en la lista de iniciadores ( 4.11.2d3a), dejando el cuerpo del constructor para
cualquier lgica adicional que sea necesaria durante la construccin [1]. La razn
argumentada es que al agruparlas as, se facilita la legibilidad de cdigo y el manejo de
excepciones en los procesos de creacin de objetos. El consejo llega al extremo de
recomendar que, si es imprescindible realizar al alguna manipulacin de estos miembros en el
cuerpo del constructor, al menos se inicien con un valor adecuado en la lista de iniciadores.
Valor que ser actualizarlo despus en el cuerpo del constructor.
La destruccin sigue el proceso inverso, de forma que la destruccin de estos objetos implica a su
vez la invocacin de los destructores de los objetos contenidos.
Ejemplo:
Al instanciar el objeto T1, el constructor de la clase Triangulo invocar tres veces al constructor
de la clase Coordenada. As mismo, el destructor de T1 tambin invocar al destructor
de Coordenada. Sin embargo, tanto la propiedad Triangulo::color como los
miembros x e yde los vrtices, permanecern sin una inicializacin especfica, aunque desde
luego, sern destruidos cuando el objeto T1 sea destruido.
4 Tipos de constructores
Una vez que se ha definido un tipo abstracto C (una clase), su utilizacin supone una operatoria
mnima que puede ser esquematizada en las siguientes sentencias:
{
C c1, c2;
C c3 = c1;
c2 = c1;
}
//
//
//
//
Creacin
Creacin a partir de un modelo
Asignacin
destruccin
Los diseadores del lenguaje decidieron que estas utilidades eran imprescindibles, y adems
pretendieron simplificar el trabajo del usuario, de forma que, aunque dejaron al programador
facultad para definir sus propios constructores, destructores y operadores, decidieron que, en caso
de no hacerlo explcitamente, el compilador debera proporcionarlos por defecto. Estos algoritmos
se denominan oficiales o de oficio para distinguirlos de los creados por el programador, a los que
Todos estos algoritmos "oficiales" suministrados por el compilador cuando no hay ninguno
explcito, tienen un comportamiento predefinido que ser comentando ms adelante. Por ahora
indicaremos que cuando existen versiones explcitas, el compilador aporta automticamente
algunos detalles si la versin explcita los omite. Estos detalles tienden a garantizar un
comportamiento correcto del algoritmo desde el punto de vista lgico y varan en funcin del
algoritmo (constructor, constructor-copia o destructor).
Es significativo que, aparte de las invocaciones explcitas o implcitas a los constructores, que
ocurren cuando se instancia deliberadamente un objeto, el compilador tambin crea infinidad de
objetos temporales (cuya existencia pasa ms o menos inadvertida) utilizando el mencionado
constructor-copia. Considere el siguiente ejemplo:
class UnaClase { ... };
UnaClase func (UnaClase) {
...
return UnaClase;
}
...
{
UnaClase obj1;
UnaClase obj2 = obj1;
UnaClase obj3 = func(obj1);
onj2 = onj3;
}
//
//
//
//
//
//
Bloque B.
L.1
L.2
L.3
L.4
L.5
Estas sentencias provocan las siguientes invocaciones (ver en la pgina adjunta un ejemplo
ejecutable de verificacin
ejemplo):
L.1: Invocacin al constructor por defecto (sin argumentos). Este constructor puede ser oficial o
explcito, segn el diseo de la clase.
L.2: Invocacin al constructor-copia (crea un objeto obj2 con el mismo contenido que obj1).
L.3: a.- Invocacin de la funcin: Es invocado el constructor-copia para crear un objeto
temporal tmp local a la funcin, e igual que el objeto obj1 pasado como argumento. Al terminar la
funcin, el objeto tmp es finalmente destruido, junto con el resto de objetos locales, mediante la
invocacin a su destructor.
b.- Invocacin del constructor-copia para crear un objeto obj3, con el mismo contenido que el
valor devuelto por la funcin.
L.4: No se invoca ningn constructor. La asignacin es totalmente distinta de la construccin y de
la construccin-copia (se realiza entre objetos ya creados).
L.5: Invocacin de los destructores de los objetos obj1, obj2 y obj3.
Tamas relacionados:
Constructores de conversin (
4.11.2d1)
Operadores de conversin (
4.9.18k)
Control de recursos (
4.1.5a)
4.11.2d1 Constructores
1 Sinopsis
Podemos imaginar que la construccin de objetos tiene tres fases:
1. instanciacin, que aqu representa el proceso de asignacin de espacio al objeto, de
forma que este tenga existencia real en memoria.
2. Asignacin de recursos. Por ejemplo, un miembro puede ser un puntero sealando a una
zona de memoria que debe ser reservada; un "handle" a un fichero; el bloqueo de un
recurso compartido o el establecimiento de una lnea de comunicacin.
3. Iniciacin, que garantiza que los valores iniciales de todas sus propiedades sean
correctos (no contengan basura).
La correcta realizacin de estas fases es importante, por lo que el creador del lenguaje decidi
asignar esta tarea a un tipo especial de funciones (mtodos) denominadas constructores. En
realidad la consideraron tan importante, que como veremos a continuacin, si el programador no
2 Descripcin
Para empezar a entender como funciona el asunto, observe este sencillo ejemplo en el que se
definen sendas clases para representar complejos; en una de ellas definimos explcitamente un
constructor; en otra dejamos que el compilador defina un constructor de oficio:
#include <iostream>
using namespace std;
class CompleX {
// Una clase para representar complejos
public:
float r; float i;
// Partes real e imaginaria
CompleX(float r = 0, float i = 0) { // L.7: construtor explcito
this->r = r; this->i = i;
cout << "c1: (" << this->r << "," << this->i << ")" << endl;
}
};
class CompX {
// Otra clase anloga
public:
float r; float i;
// Partes real e imaginaria
};
void main() {
// ======================
CompleX c1;
// L.18:
CompleX c2(1,2);
// L.19:
CompX c3;
// L.20:
cout << "c3: (" << c3.r << "," << c3.i << ")" << endl;
}
Salida:
c1: (0,0)
c2: (1,2)
c3: (6.06626e-39,1.4013e-45)
Comentario
En la clase CompleX definimos explcitamente un constructor que tiene argumentos por defecto
( ), no as en la clase CompX en la que es el propio compilador el que define un constructor de
oficio.
Es de destacar la utilizacin explcita del puntero this ( 4.11.6) en la definicin del constructor
(L.8/L.9). Ha sido necesario hacerlo as para distinguir las propiedades i, j de las variables locales
4 Invocacin de constructores
Al margen de la particularidad que representan sus invocaciones implcitas, en general su
invocacin sigue las pautas del resto de los mtodos. Ejemplos:
X x1;
X::X();
X x2 = X::X()
X x3 = X();
X x4();
//
//
//
//
//
Nota: observe como la nica sentencia vlida con invocacin explcita al constructor (L.4) es
un caso de invocacin de funcin miembro muy especial desde el punto de vista sintctico
(esta sintaxis no est permitida con ningn otro tipo de funcin-miembro, ni siquiera con
funciones estticas o destructores). La razn es que los constructores se diferencian de todos
los dems mtodos no estticos de la clase en que no se invocan sobre un objeto (aunque
tienen puntero this
4.11.6). En realidad se asemejan a los dispositivos de asignacin de
memoria, en el sentido que son invocados desde un trozo de memoria amorfa y la convierten
en una instancia de la clase [7].
Como ocurre con los tipos bsicos (preconstruidos en el lenguaje), si deseamos crear objetos
persistentes de tipo abstracto (definidos por el usuario), debe utilizarse el operador new (
4.9.20). Este operador est ntimamente relacionado con los constructores. De hecho, para
invocar la creacin de un objeto a traves de l, debe existir un constructor por defecto
.
Si nos referimos a la clase CompleX definida en el ejemplo
, las sentencias:
{
CompleX* pt1 = new(CompleX);
CompleX* pt2 = new(CompleX)(1,2);
}
provocan la creacin de dos objetos automticos, los punteros pt1 y pt2, as como la creacin de
sendos objetos (annimos) en el montn. Observe que ambas sentencias suponen un invocacin
implcita al constructor. La primera al constructor por defecto sin argumentos, la segunda con los
argumentos indicados. En consecuencia producirn las siguientes salidas:
c1: (0,0)
c1: (1,2)
Observe tambin, y esto es importante, que los objetos pt1 y pt2 son destruidos automticamente
al salir de mbito el bloque. No as los objetos sealados por estos punteros (ver comentario al
respecto
4.11.2d2).
class X {
public:
X();
};
// definicin de la clase X
// constructor de la clase X
5.2 No se puede obtener su direccin, por lo que no pueden declararse punteros a este tipo de
mtodos.
5.3 No pueden declararse virtuales (
class C {
...
virtual C();
};
4.11.8a). Ejemplo:
// Error !!
La razn est en la propia idiosincrasia de este tipo de funciones. En efecto, veremos que declarar
que un mtodo es virtual ( 4.11.8a) suponeindicar al compilador que el modo concreto de
operar la funcin ser definido ms tarde, en una clase derivada. Sin embargo, un constructor debe
conocer el tipo exacto de objeto que debe crear, por lo que no puede ser virtual.
5.4 Otras peculiaridades de los constructores es que se declaran sin devolver nada, ni
siquiera void, lo que no es bice para que el resultado de su actuacin (un objeto) s pueda ser
utilizado como valor devuelto por una funcin:
class C { ... };
...
C foo() {
return C();
}
5.5 No pueden ser heredados, aunque una clase derivada puede llamar a los constructores y
destructores de la superclase siempre que hayan sido declarados public o protected (
4.11.2a). Como el resto de las funciones (excepto main), los constructores tambin pueden ser
sobrecargados; es decir, una clase puede tener varios constructores.
En estos casos, la invocacin (incluso implcita) del constructor adecuado se efectuar segn los
argumentos involucrados. Es de destacar que en ocasiones, la multiplicidad de constructores
puede conducir a situaciones realmente curiosas; incluso se ha definido una palabra
clave, explicit, para evitar los posibles efectos colaterales
.
5.5 Un constructor no puede ser friend (
5.6 Una peculiaridad sintctica de este tipo de funciones es la posibilidad de incluir iniciadores (
4.11.2d3), una forma de expresar la inicializacin de variables fuera del cuerpo del constructor.
Ejemplo:
class X {
const int i;
char c;
public:
X(int entero, char caracter): i(entero), c(caracter) { };
};
5.7 Como en el resto de las funciones, los constructores pueden tener argumentos por
defecto. Por ejemplo, el constructor:
X::X(int, int = 0)
puede aceptar uno o dos argumentos. Cuando se utiliza con uno, el segundo se supone que es un
cero int.
De forma anloga, el constructor
X::X(int = 5, int = 6)
puede aceptar dos, uno, o ningn argumento. Los valores por defecto proporcionan la informacin
necesaria cuando faltan datos explcitos.
Observe que un constructor sin argumentos, como X::X(), no debe ser confundido
con X::X(int=0), que puede ser llamado sin argumentos o con uno, aunque en realidad siempre
tendr un argumento. En otras palabras: que una funcin pueda ser invocada sin
argumentosno implica necesariamente que no los acepte.
5.9 Los constructores de las variables globales son invocados por el mdulo inicial antes de que
sea llamada la funcin main y las posibles funciones que se hubiesen instalado mediante la
directiva #pragma startup ( 1.5).
5.10 Los objetos locales se crean tan pronto como se inicia su mbito. Tambin se invoca
implcitamente un constructor cuando se crea, o copia, un objeto de la clase (incluso temporal). El
hecho de que al crear un objeto se invoque implcitamente un constructor por defecto si no se
invoca ninguno de forma explcita, garantiza que siempre que se instancie un objeto ser
inicializado adecuadamente.
En el ejemplo que sigue se muestra claramente como se invoca el constructor tan pronto como se
crea un objeto.
#include <iostream>
using namespace std;
class A {
// definicin de una clase
public:
int x;
A(int i = 1) {
// constructor por defecto
x = i;
cout << "Se ha creado un objeto" << endl;
}
};
int main() {
// =========================
A a;
// se instancia un objeto
cout << "Valor de a.x: " << a.x << endl;
return 0;
}
Salida:
Se ha creado un objeto
Valor de a.x: 1
5.11 El constructor de una clase no puede admitir la propia clase como argumento (se dara lugar
a una definicin circular). Ejemplo:
class X {
public:
X(X);
};
// Error: ilegal
5.12 Los parmetros del constructor pueden ser de cualquier tipo, y aunque no puede aceptar su
propia clase como argumento, en cambio s pueden aceptar una referencia a objetos de su propia
clase, en cuyo caso se denomina constructor-copia (su sentido y justificacin lo exponemos con
ms detalle en el apartado correspondiente
4.11.2d4).
Ejemplo:
class X {
public:
X(X&);
};
// Ok. correcto
Aparte del referido constructor-copia, existe otro tipo de constructores de nombre especfico:
el constructor oficial y el constructor por defecto
.
6 Constructor oficial
Si el programador no define explcitamente ningn constructor, el compilador proporciona uno por
defecto al que llamaremos oficial o de oficio. Es pblico, "inline" (
4.11.2a), y definido de forma
que no acepta argumentos. Es el responsable de que funcionen sin peligro secuencias como esta:
class A {
int x;
};
...
A a;
Recordemos que el constructor de oficio invoca implcitamente los constructores de oficio de todos
los miembros. Si algunos miembros son a su vez objetos abstractos, se invocan sus constructores.
As sucesivamente con cualquier nivel de complejidad hasta llegar a los tipos bsicos
(preconstruidos en el lenguaje
2.2) cuyos constructores son tambin invocados. Recordar que
los constructores de los tipos bsicos inician (reservan memoria) para estos objetos, pero no los
inicializan con ningn valor concreto. Por lo que en principio su contenido es impredecible
(basura) [1]. Dicho en otras palabras: el constructor de oficio se encarga de preparar el ambiente
para que el objeto de la clase pueda operar, pero no garantiza que los datos contenidos sean
correctos. Esto ltimo es responsabilidad del programador y de las condiciones de "runtime". Por
ejemplo:
struct Nombre {
char* nomb;
};
struct Equipo {
Nombre nm;
size_t sz;
};
struct Liga {
int year;
char categoria;
Nombre nLiga;
Equipo equipos[10];
};
...
Liga primDiv;
En este caso la ltima sentencia inicia primDiv mediante una invocacin al constructor por
defecto de Liga, que a su vez invoca a los constructores por defecto de Nombre y Equipo para
crear los miembros nLiga y equipos (el constructor de Equipo es invocado diez veces, una por
cada miembro de la matriz). A su vez, cada invocacin a Equipo() produce a su vez una
invocacin al constructor por defecto deNombre (size_t es un tipo bsico y no es invocado su
constructor
4.9.13). Los miembros nLiga y equipos son iniciados de esta forma, pero los
miembros year y categoria no son inicializados ya que son tipos simples, por lo que pueden
contener basura.
Si el programador define explcitamente cualquier constructor, el constructor oficial deja de
existir. Pero si omite en l la inicializacin de algn tipo abstracto, el compilador aadir por su
cuenta las invocaciones correspondientes a los constructores por defecto de los miembros omitidos
( Ejemplo).
#include <iostream>
using namespace std;
class Punto {
public: int coord[2];
Punto(int x = 0, int y = 0) {
coord[0] = x; coord[1] = y;
}
};
// construtor explcito
// inicializa
int main() {
// ==================
Punto p1(10, 20);
// L.8: Ok.
cout << "Punto p1; X == " << coord[0] << "; Y == " << coord[1] << endl;
}
8.1 La anterior no es por supuesto la nica causa que hace necesaria la existencia de
constructores explcitos. Ms frecuente es el caso de que algunas de las variables de la clase
deban ser persistentes. Por ejemplo, supongamos que en el caso anterior necesitamos que la
matriz que almacena las coordenadas necesite este tipo de almacenamiento. En este caso, puesto
que la utilizacin del especificador static aplicado a miembros de clase puede tener efectos
colaterales indeseados ( 4.11.7), el nico recurso es situar el almacenamiento en el montn (
1.3.2), para lo que utilizamos el operador new ( 4.9.20) en un constructor definido al
efecto. La definicin de la clase tendra el siguiente aspecto [8]:
class Punto {
public: int* coord;
Punto(int x = 0, int y =
coord = new int[2];
coord[0] = x; coord[1]
cout << "Creado punto;
<< coord[0] << ";
}
};
0) {
= y;
X == "
Y == " << coord[1] << endl;
//
//
//
//
//
invocacin
invocacin
invocacin
invocacin
invocacin
implcita
implcita
explcita
implcita
implcita
con
con
sin
con
argumentos
argumentos
argumentos
argumentos
9 Orden de construccin
Dentro de una clase los constructores de sus miembros son invocados antes que el constructor
existente dentro del cuerpo de la propia clase. Esta invocacin se realiza en el mismo orden en que
se hayan declarado los elementos. A su vez, cuando una clase tiene ms de una clase base
(herencia mltiple 4.11.2c), los constructores de las clases base son invocados antes que el de
la clase derivada y en el mismo orden que fueron declaradas. Por ejemplo en la inicializacin:
class Y {...}
class X : public Y {...}
X one;
los constructores son llamados en el siguiente orden:
Y();
X();
9.2 Si una clase virtual deriva de otra no virtual, primero se invoca el constructor de la clase base
(no virtual), de forma que la virtual (derivada) pueda ser construida correctamente. Por ejemplo, el
cdigo:
class X : public Y, virtual public Z
X one;
origina el siguiente orden de llamada en los constructores:
Z();
Y();
X();
Un ejemplo ms complicado:
class base;
class base2;
class level1 : public base2, virtual public base;
class level2 : public base2, virtual public base;
class toplevel : public level1, virtual public level2;
toplevel view;
//
//
//
//
//
//
//
toplevel();
9.3 Si una jerarqua de clases contiene mltiples instancias de una clase base virtual, dicha base
virtual es construida solo una vez. Aunque si existen dos instancias de la clase base: virtual y no
virtual, el constructor de la clase es invocado solo una vez para todas las instancias virtuales y
despus una vez para cada una de las instancias no virtuales.
9.4 En el caso de matrices de clases, los constructores son invocados en orden creciente de
subndices.
}
};
int main() {
D d("Hola mundo");
}
// =============
// invocacin implcita a constructor D
Salida:
Constructor-base
Funcion-base: Hola mundo
Constructor-derivado
Nota: la invocacin de destructores (
4.11.2d2) se realiza en orden inverso a los
constructores. Las clases derivadas se destruyen antes que las clases-base [2]. Por esta
razn el mecanismo virtual tambin est deshabilitado en los destructores (lo que no tiene
nada que ver con que los destructores puedan ser en s mismos funciones
virtuales
4.11.2d2). As pues, en la ejecucin de un destructor solo se invocan las
definiciones locales de las funciones implicadas. De lo contrario se correra el riesgo de
referenciar la parte derivada del objeto que ya estara destruida.
11 Constructores de conversin
Normalmente a una clase con constructor de un solo parmetro puede asignrsele un valor que
concuerde con el tipo del parmetro. Este valor es automticamente convertido de forma implcita
en un objeto del tipo de la clase a la que se ha asignado. Por ejemplo, la definicin:
class X {
public:
X();
X(int);
X(const char*, int = 0);
};
// constructor C-1
// constructor C-2
// constructor C-3
en la que se han definido dos constructores que pueden ser utilizados con un solo
argumento, permite que las siguientes asignaciones sean legales:
void f() {
X a;
X b = X();
X c = X(1);
X d(1);
X e = X("Mexico");
X f("Mexico");
X g = 1;
X h = "Madrid";
a = 2;
}
//
//
//
//
//
//
//
//
//
Ok invocado C-1
Ok dem.
Ok invocado C-2
Ok igual que el anterior
Ok invocado C-3
Ok igual que el anterior
L.1 Ok.
L.2 Ok.
L.3 Ok.
clase). Como necesita crear un nuevo objeto, utilizar un constructor, de forma que busca si hay
uno adecuado en X que acepte como argumento el tipo situado a la derecha. El resultado es que
el compilador supone un constructor implcito a la derecha de L.1:
X a = X(1);
// L.2bis
La situacin en L.3 es completamente distinta, ya que en este caso ambos operandos son objetos
ya construidos. Para poder realizar la asignacin, el compilador intenta convertir el tipo del Rvalue
al tipo del Lvalue, para lo cual, el mecanismo de conversin de tipos busca si existe un constructor
adecuado en X que acepte el operando derecho. Caso de existir se crear un objeto temporal
tipoX que ser utilizado como Rvalue de la asignacin. La asignacin propiamente dicha es
realizada por el operador correspondiente (explcito o implcito) de X. La pgina adjunta incluye un
ejemplo que muestra grficamente el proceso seguido (
Ejemplo)
Este tipo de conversin automtica se realiza solo con constructores que aceptan un argumento o
que son asimilables (como C-2), y suponen una conversin del tipo utilizado como argumento al
tipo de la clase. Por esta razn son denominadas conversiones mediante constructor, y a este tipo
de constructores constructores de conversin ("Converting constructor"). Su sola presencia
habilita no solo la conversin implcita, tambin la explcita. Ejemplo:
class X {
public:
X(int);
};
// constructor C-2
la mera existencia del constructor C-2 en la clase X, permite las siguientes asignaciones:
void f() {
X a = X(1)
X a = 1;
a = 2;
a = (X) 2;
a = static_cast<X>(2);
}
//
//
//
//
//
L1:
Ok.
Ok.
Ok.
Ok.
Temas relacionados
Operadores de conversin (
4.9.18k)
Conversin automtica a tipos simples (
4.13.6)
12 Constructor explicit
El problema es que en ocasiones el comportamiento descrito en el epgrafe anterior puede resultar
indeseable y enmascarar errores. Es posible evitarlo declarando el constructor de la clase con la
palabra clave explicit, dando lugar a los denominados constructores explicit [3]. En estos casos,
los objetos de la clase solo podrn recibir asignaciones de objetos del tipo exacto. Cualquier otra
asignacin provocar un error de compilacin.
// L.1 Ok.
// L.2 Ok.
// L.3 Ok.
C c(1);
}
Adems, como las clases derivadas necesitan invocar los constructores de las superclases para
instanciar sus objetos, caso de no ser protegidos o pblicos tambin pueden existir limitaciones
para su creacin. Ejemplo:
class B {
int x;
B (): x(10) {}
};
class D : public B {
...
};
void foo() {
D d;
// Error!! no appropriate default constructor available
...
}
Puesto que los miembros private o protected no pueden ser accedidos desde el exterior de la
clase, este tipo de constructores se suelen utilizar siempre a travs de funciones-miembro pblicas
con objeto de garantizar cierto control sobre los objetos creados. El esquema de utilizacin sera el
siguiente:
class C {
C(int n) { /* constructor privado */ }
public:
static C makeC(int m) {
...
return C(m);
}
...
};
void foo() {
C c = C::makeC(1);
}
// Ok.
Observe que makeC() es esttica para que pueda ser invocada con independencia de la
existencia de cualquier objeto (
4.11.7). Observe tambin que mientras una expresin como:
C c = C(1);
es una invocacin al constructor, en cambio, la sentencia
C c = C::makeC(1);
es una invocacin al operador de asignacin, ya que la funcin devuelve un objeto que ser
tomado como Rvalue de la asignacin.
Esta tcnica, que utiliza constructores privados o protegidos junto con mtodos pblicos para
accederlos, puede prevenir algunas conversiones de tipo no deseadas, pudiendo constituir una
alternativa al uso de constructores explicit (12 ).
4.11.2d2 Destructores
1 Sinopsis
Los destructores son un tipo especial de funcin miembro, estrechamente relacionados con los
constructores. Son tambin funciones que no devuelven nada (ni siquiera void). Tampoco aceptan
ningn parmetro, ya que la destruccin de un objeto no acepta ningn tipo de opcin o
especificacin particular y es idntica para todos los objetos de la clase. Los destructores no
pueden ser heredados, aunque una clase derivada puede llamar a los destructores de su
superclase si no han sido declarados privados (son pblicos o protegidos). Lo mismo que ocurre
con los constructores, tampoco puede obtenerse su direccin, por lo que no es posible establecer
punteros a este tipo de funciones.
La misin ms comn de los destructores es liberar la memoria asignada por los constructores,
aunque tambin puede consistir en desasignar y/o liberar determinados recursos asignados por
estos. Por ejemplo, cerrar un fichero; una lnea de comunicacin, o desbloquear un recurso
compartido que hubiera sido bloqueado previamente por el constructor.
Se ha sealado que, si el programador no define uno explcitamente, el compilador C++
proporciona un destructor de oficio, que es declarado pblico y puede ser invocado sin
argumentos. Por lo general en la mayora de los casos este destructor de oficio es suficiente, por lo
que el programador no necesita definir uno por s mismo, a no ser que la clase incluya la
inicializacin de objetos persistentes (
1.3.2). Por ejemplo, matrices que necesiten del
operador new en el constructor para su inicializacin, en cuyo caso es responsabilidad del
programador definir un destructor adecuado (ver ejemplo
).
Los destructores son invocados automticamente (de forma implcita) por el programa en multitud
de ocasiones; de hecho es muy raro que sea necesario invocarlos explcitamente. Su misin es
limpiar los miembros del objeto antes que el propio objeto se auto-destruya.
2 Declaracin
Los destructores se distinguen porque tienen el mismo nombre que la clase a que pertenecen
precedido por la tilde ~ para simbolizar su estrecha relacin con los constructores que utilizan el
mismo nombre (son el "complemento" de aquellos). Ejemplo:
class X {
public:
~X();
};
...
X::~X() {
...
}
// destructor de la clase X
// definicin (off-line) del destructor
Ejemplo:
La clase Punto definida en el epgrafe anterior (
4.11.2d1) sera un buen exponente del caso en
que es necesario definir un destructor explcito que se encargue de las correcta destruccin de los
miembros. En efecto, manteniendo aquella definicin, una sentencia del tipo:
{
...
Punto p1(2,3);
...
}
provoca la creacin de un objeto en memoria dinmica. El miembro coord es un puntero-a-int que
seala un rea en el montn capaz para albergar dos enteros. Cuando la ejecucin sale del mbito
del bloque en que se ha creado el objeto, es invocado el destructor de oficio y el objeto es
destruido, incluyendo su nico componente, el puntero coord; sin embargo el rea sealada por
este permanece reservada en el montn, y por tanto irremediablemente perdida.
La forma sensata de utilizar tales objetos sera modificando la definicin de la clase para aadirle
un destructor adecuado. La versin correcta tendra el siguiente aspecto:
class Punto {
public: int* coord;
Punto(int x = 0, int y = 0) { // construtor
coord = new int[2];
coord[0] = x; coord[1] = y;
}
~Punto() {
// destructor
delete [] coord;
// L.8:
};
En este caso, la sentencia de la lnea 8 provoca que al ser invocado el destructor del objeto, se
desasigne el rea del montn sealada por el puntero (recuerde que, al igual que el resto de las
funciones-miembro, los destructores tambin tienen un argumento oculto this, por lo que la funcin
sabe sobre que objeto tiene que operar en cada caso).
3 Invocacin
Como hemos sealado, los destructores son invocados automticamente por el compilador, y es
muy raro que sea necesario invocarlos explcitamente.
3.1 Invocacin explcita de destructores
En caso necesario los destructores pueden ser invocados explcitamente de dos formas:
indirectamente, mediante una llamada a delete ( 4.9.21) o directamente utilizando el nombre
cualificado completo.
Ejemplo
class X {...};
...
// X es una clase
{
X obj1;
X* ptr = new(X)
X* pt2 = &obj1;
...
pt2>X::~X();
// pt2->~X();
// obj1.~X();
X::~X();
delete ptr;
}
Comentario
L.4 crea el objeto obj1 en la pila (
1.3.2), se trata de un objeto automtico, y en cuanto el
bloque salga de mbito, se producir una llamada a su destructor que provocar su
eliminacin. Sin embargo, el objeto annimo sealado por ptr es creado en el montn.
Observe que mientras ptr es tambin un objeto automtico, que ser eliminado al salir del bloque,
el objeto al que seala es persistente y su destructor no ser invocado al salir de mbito el bloque.
Como se ve en el punto siguiente, en estos casos es imprescindible una invocacin explcita al
destructor mediante el operador delete (cosa que hacemos en L.12), en caso contrario, el espacio
ocupado por el objeto se habr perdido.
Es muy importante advertir que la invocacin explcita al destructor de obj1 en L.8 (o su versiones
equivalentes L.9 y L.10) son correctas, aunque muy peligrosas [2]. En efecto, en L.8 se produce la
destruccin del objeto, pero en el estado actual de los compiladores C++, que no son
suficientemente "inteligentes" en este sentido [3], al salir el bloque de mbito vuelven a invocar los
destructores de los objetos automticos creados en su interior, por lo que se producir un error de
ejecucin irrecuperable (volcado de memoria si corremos bajo Linux).
3.1.1 Los objetos que han sido creados con el operador new ( 4.9.20) deben
destruirse obligatoriamente con una llamada explcita al destructor. Ejemplo:
#include <stdlib.h>
class X {
// clase
public:
...
~X(){};
// destructor de la clase
};
void* operator new(size_t size, void *ptr) {
return ptr;
}
char buffer[sizeof(X)];
// matriz de caracteres, del tamao de X
void main() {
X* ptr1 = new X;
X* ptr2;
ptr2 = new(&buffer) X;
...
delete ptr1;
//
//
//
//
========================
puntero a objeto X creado con new
puntero a objeto X
se inicia con la direccin de buffer
ptr2>X::~X();
buffer
}
1052