Beruflich Dokumente
Kultur Dokumente
3.2.2 En el ejemplo que sigue se ha modificado ligeramente el cdigo anterior para demostrar
como el destructor es invocado incluso cuando la salida de mbito se realiza mediante una
sentencia de salto (omitimos la salida, que es idntica a la anterior):
#include <iostream>
using namespace std;
class A {
public:
int x;
A(int i = 1) { x = i; } // constructor por defecto
~A() {
// destructor
cout << "El destructor ha sido invocado" << endl;
}
};
int main() {
// =========================
{
A a;
// se instancia un objeto
cout << "Valor de a.x: " << a.x << endl;
goto FIN;
}
FIN:
return 0;
}
3.2.3 Una tercera versin, algo ms sofisticada, nos muestra como la invocacin del destructor se
realiza incluso cuando la salida de mbito se realiza mediante el mecanismo de salto del
manejador de excepciones, y cmo la invocacin se realiza para cualquier objeto, incluso temporal,
que deba ser destruido (
Ejemplo).
Recordar que que cuando los punteros a objetos salen de mbito, no se invoca implcitamente
ningn destructor para el objeto, por lo que se hace necesaria una invocacin explcita al
operador delete (
4.9.21) para destruir el objeto (3.1.1
).
5 Destructores virtuales
Como cualquier otra funcin miembro, los destructores pueden ser declarados virtual (
4.11.8a).
El destructor de una clase derivada de otra cuyo destructor es virtual, tambin es
virtual (
4.11.8a).
La existencia de un destructor virtual permite que un objeto de una subclase pueda ser
correctamente destruido por un puntero a su clase-base [4].
Ejemplo:
class B {
// Superclase (polimrfica)
...
virtual ~B(); // Destructor virtual
};
class D : public B {
// Subclase (deriva de B)
...
~D(); // destructor tambin virtual
};
void func() {
B* ptr = new D;
subclase
...
delete ptr;
}
Comentario
Aqu el mecanismo de llamada de las funciones virtuales permite que el operador delete invoque
al destructor correcto, es decir, al destructor ~Dde la subclase, aunque se invoque mediante el
puntero ptr a la superclase B*. Si el destructor no hubiese sido virtual no se hubiese invocado el
destructor derivado ~D, sino el de la superclase ~B, dando lugar a que los miembros privativos de
la subclase no hubiesen sido desasignados. Tendramos aqu un caso tpico de "misteriosas"
prdidas de memoria, tan frecuentes en los programas C++ como difciles de depurar.
A pesar de todo, el mecanismo de funciones virtuales puede ser anulado utilizando un operador de
resolucin adecuado (
4.11.8a). En el ejemplo anterior podra haberse puesto:
void foo (B&, B&);
superclase!!void func() {
D d1, d2;
foo(d1, d2);
}
public:
~rojo() {
// tambin destructor virtual
std::cout << "Destructor de rojo" << std::endl;
}
};
class rojobrillante : public rojo { // clase derivada (nieta)
public:
~rojobrillante() {
// tambin destructor virtual
std::cout << "Destructor de rojobrillante" << std::endl;
}
};
int main() {
// ===========
color* palette[3];
// matriz de tres punteros a tipo color
palette[0] = new rojo;
// punteros a tres objetos en algn sitio
palette[1] = new rojobrillante;
palette[2] = new color;
// llamada a los destructores de rojo y color (padre).
delete palette[0];
// llamada a destructores de rojobrillante, rojo (padre) y color (abuelo)
delete palette[1];
// llamada al destructor de la clase raz
delete palette[2];
return 0;
}
Salida:
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
de
de
de
de
de
de
rojo
color
rojobrillante
rojo
color
color
Comentario
Si los destructores no se hubiesen declarado virtuales, las sentencias delete
palette[0], delete palette[1], y delete palette [2]solamente hubiesen invocado el
destructor de la clase raz color. Lo que no hubiese destruido correctamente los dos primeros
elementos, que son del tipo rojo (hija) y rojobrillante (nieta).
2 Asignacin directa
Es el caso siguiente:
class X {
static int is;
public:
int i;
char c;
};
// El compilador proporciona un constructor por defecto
int X::is = 33;
...
int main() {
X x;
x.i = 1;
x.c = 'a';
...
}
========
L.9: invocacin implcita al constructor por defecto
asignacin directa de miembro
dem
Este sistema es permitido, aunque muestra una tcnica de programacin deficiente, pues todas las
variables deben ser pblicas y viola el principio de encapsulacin. La expresin de L.9 exige que
haya un constructor por defecto (puede ser el proporcionado automticamente por el compilador si
no se ha definido algn otro de forma explcita) [2].
Recordemos que la asignacin de L.8 es un caso especialsimo. Solo es posible con variables
miembro estticas (sean pblicas, protegidas o privadas) y que este tipo de asignacin es posible
incluso antes de haber instanciado ningn objeto concreto de la clase (
4.11.7).
3 Lista de iniciadores
Tambin es permitida una variacin sintctica de la anterior aunque ms compacta:
X x = {1, 'a'};
Este tipo de iniciacin, que utiliza una lista de iniciadores entre corchetes (como en el caso de las
matrices), exige que todas las variables sean pblicas, ninguna e ellas sea una clase, y no se haya
definido un constructor explcito (es el caso tpico de las estructuras
4.5.2) [1]. Los iniciadores
de la lista deben estar en el mismo orden en que aparecen en la definicin de la clase.
3.1 Ejemplo:
#include <iostream.h>
class Punto {
public: int x; int y;
};
int main() {
// ===================
Punto p = {1,2};
cout << "Valor p.x = " << p.x << endl;
}
Salida:
Valor p.x = 1
3.2 Esta tcnica es es vlida incluso con clases anidadas siempre que cumplan con las
condiciones sealadas:
#include <iostream>
using namespace std;
class Vector {
public: float x, y;
class Punto {
public: int x, y;
};
Punto pa;
};
void main() {
// ============
Vector v1 = { 2.0, 3.0, {4, 5}};
Vector v2 = { 2.0, 3.0, 4, 5 };
cout << "V1.x = " << v1.x << endl;
3.3 Sin embargo esta tcnica no es vlida si las variables de la lista son instancias de una clase.
Ejemplo:
class Punto { public: int x, y; };
class Triangulo {
public: Punto p1, p2, p3;
};
...
Punto p1 = {1, 2}, p2 = {3, 4}, p3 = {5, 6}; // Ok.
Triangulo t1 = { p1, p2, p3};
// Error!!
Triangulo t2 = {{1, 2},{3, 4},{5, 6}};
// Ok.
4 Lista de argumentos
Si la clase tiene un constructor explcito, puede utilizarse una invocacin implcita o explcita al
constructor con una lista de argumentos entre parntesis, como se muestra en el ejemplo.
class X {
int i;
// privadas por defecto
char c;
public:
// a partir de aqu todos los miembros son pblicos
X(int entero, char caracter){
// Constructor explcito
i = entero;
c = caracter;
};
};
...
En este caso, caben dos sintaxis para invocar al constructor (ambas con argumentos):
X x(1, 'a');
X x = X(1, 'a');
// invocacin implcita
// invocacin explcita
Observe que en este caso no sera ya posible utilizar la iniciacin del ejemplo anterior
existe un constructor explcito:
X x = {1, 'a'};
// Error:
, ya que
Observe tambin la definicin del constructor. Como funcin-miembro de clase, tiene acceso
directo a todas sus variables, incluso las privadas, por lo que en su cuerpo podemos referirnos
directamente a ellas. Es decir, podemos utilizar las expresiones:
i = entero;
c = caracter;
en vez de:
X::i = entero;
X::c = caracter;
que seran equivalentes aunque innecesarias. Esta capacidad permanece incluso si la definicin
del constructor (o cualquier otro mtodo) se ha sacado fuera del cuerpo de la definicin de la clase.
Es decir, aunque hubiese sido:
class X {
int i;
char c;
public:
X(int, char);
};
4.1 Ejemplo
#include <iostream.h>
class C {
public:
int x; int y;
C (int i, int j) { x = i; y = j; }
};
// Constructor
int main() {
// =============
// C c = {1,2};
Error!! forma no permitida aqu
C c = C(1,2);
// Ok. invocacin explcita al constructor
cout << "Valor c.x = " << c.x << endl;
}
Salida:
Valor c.x = 1
inicio, pero entonces es preciso disponer de valores por defecto para que pueda ser utilizado como
constructor por defecto. Es el caso del siguiente ejemplo:
class X {
int i;
char c;
public:
// a partir de aqu todos los miembros son pblicos
X(int entero = 0, char caracter = 0){ // C1: constructor-1
i = entero;
c = caracter;
};
X(const X& obj) {
// C2: constructor-2
X* ptr = new X;
ptr.i = obj.i;
ptr.c = obj.c;
};
};
Comentario
Para exponer la casustica de forma ms completa, hemos definido dos constructores para la clase
del ejemplo. Al primero le asignamos argumentos por defecto, la razn es que pueda servir
como constructor por defecto, pues el operador new del cuerpo del segundo necesita que exista
un constructor por defecto definido en la clase (
4.9.20).
El funcionamiento del segundo es un tanto especial; acepta una referencia a un objeto de la propia
clase. Crea un objeto nuevo del tipo de la clase con new, e inicia sus elementos copiando de sus
homnimos correspondientes. Veremos que, debido a su forma especial de trabajar, este tipo de
constructor se denomina constructor copiador o sencillamente constructor-copia (
4.11.2d4).
Una vez definida la clase y sus constructores, ms tarde pueden ser llamados con los parmetros
adecuados para construir un objeto inicializado a los valores deseados:
X* ptr;
ptr>X::X(3, 'a');
X x = X(4, 'b');
X y(5, 'c');
X z;
//
//
//
//
//
Las tres primeras formas de invocar al constructor le pasan argumentos, la ltima utiliza los
argumentos por defecto y todas son igualmente correctas. Observe que en la primera tenemos un
objeto annimo, solo puede ser accedido a travs de su puntero. La tercera puede enunciarse
diciendo: las clases con constructor pueden ser inicializadas con una lista de expresiones entre
parntesis; expresiones que actan como argumentos de una invocacin implcita del constructor.
El segundo constructor C2
, puede servir igualmente a nuestros fines, solo que ahora debemos
pasarle como argumento una referencia a un objeto de la clase. Por ejemplo:
X uno = X(x);
X dos = y;
X bis(z);
// invocacin implcita a C2
// invocacin implcita a C2
// invocacin implcita a C2
5.1 Ejemplo
Todo lo anterior en un cdigo funcional:
#include <iostream.h>
class X {
public:
int i;
char c;
X(int = 0, char = NULL);
// C1: prototipo (constructor por
defecto)
X(const X&);
// C2: prototipo (constructor copia)
};
X::X(int entero, char caracter) { // definicin de C1
i = entero;
c = caracter;
}
X::X(const X& obj) {
// definicin de C2
X* ptr = new X;
ptr->i = obj.i;
ptr->c = obj.c;
}
void main() {
X * ptr;
ptr = &X::X(1, 'a');
X x = X(2, 'b');
X y(3, 'c');
// =========================
// se crean tres objetos
cout << "Valor ?: " << ptr->i << ", " << ptr->c << endl;
cout << "Valor x: " << x.i << ", " << x.c << endl;
cout << "Valor y: " << y.i << ", " << y.c << endl;
X uno = X(x);
X dos = y;
X bis(z);
// invocacin implcita a C2
// invocacin implcita a C2
// invocacin implcita a C2
cout << "Valor uno: " << uno.i << ", " << uno.c << endl;
cout << "Valor dos: " << dos.i << ", " << dos.c << endl;
cout << "Valor bis: " << bis.i << ", " << bis.c << endl;
}
Salida:
Valor: 1, a
Valor: 2, b
Valor: 3, c
Comentario
Observe que se ha sacado la definicin de los constructores C1 y C2 fuera del cuerpo de la clase
(dejamos en esta solo el prototipo) utilizando una propiedad de todos los mtodos: la definicin
puede estar fuera del cuerpo de la propia clase si se aade el especificador adecuado.
// S1:
supone la asignacin de todos los miembros de c2 en sus homnimos de c1. Sin embargo,
cuando el Lvalue implica la declaracin de un nuevo objeto, la expresin
C c1 = c2;
// S2:
no implica ninguna asignacin. En realidad la sintaxis anterior es una convencin para representar
la invocacin de un constructor especial, denominado constructor-copia que acepta una
referencia al objeto a copiar c2 (Rvalue de la expresin) y produce un nuevo objeto c1 que es un
clon del anterior. Podramos suponer que la sentencia S2 es equivalente a una invocacin del tipo:
C::C(c2);
y que el objeto producido se identifica con el nombre c1 (recordemos que los constructores no
"devuelven" nada).
2 El constructor-copia
El constructor-copia de la clase X es aquel que puede ser invocado con un argumento, que es
precisamente una referencia a la propia clase Xcomo en cualquiera de las definiciones siguientes:
X::X(X&) { /*...*/ };
X::X(const X&){ /*...*/ };
X::X(const X&, int i) { /*...*/ };
En general "copiar" un objeto supone la generacin fsica de otro objeto, pero no siempre el nuevo
es necesariamente una rplica exacta del original (aunque pueda parecerlo). En ocasiones el
nuevo objeto es un manejador "handle" del original, de forma que el usuario percibe "como si"
estuviera ante una copia real. Este tipo de copia, denominada lgica o ligera, presenta la ventaja
de su rapidez y economa de espacio. La duplicacin completa de objetos grandes puede ser
costosa en trminos de espacio y tiempo, adems de no ser siempre estrictamente necesaria o
conveniente.
Las copias profundas, tambin denominadas fsicas, suponen la replicacin completa del objeto
original, reproduciendo todos sus miembros ("Memberwise") a todos los niveles de anidamiento
(los objetos copiados pueden ser tipos abstractos cuyos miembros pueden ser a su vez tipos
abstractos, cuyos miembros pueden ser a su vez tipos abstractos, y as sucesivamente con
cualquier nivel de anidacin).
2.1 Este tipo de constructores son invocados automticamente por el compilador en muchas
circunstancias (siempre que se presenta la necesidad de crear un nuevo objeto igual a otro
existente):
Cuando se pasa un objeto (por valor) como argumento a una funcin; en cuyo caso se crea
una copia del objeto en la funcin invocada ( 4.4.5). Ejemplo:
class C { .... } c1;
void func(C c) { ...; } // funcin que acepta un objeto
...
func(c1);
Cuando una funcin devuelve un objeto, en cuyo caso se crea una copia del objeto en la
funcin invocante (
4.4.7). Ejemplo:
class C { ... } c1;
C func(C& ref) {
// funcin que devuelve un objeto
...
return ref;
}
...
{
...
c2 = func(c1);
// se crea una copia local del objeto ref
}
};
// L.1 invocacin implcita al constructor por
// L.2 invocacin implcita al constructor-copia
// L.3 dem (variacin sintctica del anterior)
// L.4 invocacin del operador de asignacin
// Ok.
2.3 Ejemplo:
A continuacin exponemos el ejemplo de clase para la que se define un constructor-copia (un
perfeccionamiento de la clase Punto ya comentada
4.11.2d2). Aunque se trata de un caso
sencillo, ilustra con claridad la tcnica seguida, as como el proceso de invocacin automtica de
dichos mtodos realizada por el compilador.
#include <iostream>
using namespace std;
#define Abcisa coord[0]
#define Ordenada coord[1]
class Punto {
public:
int* coord;
Punto(int x = 1, int y = 2) {
// construtor por defecto
coord = new int[2];
Abcisa = x; Ordenada = y;
cout << "Creado objeto: X == "
<< Abcisa << "; Y == " << Ordenada << endl;
}
~Punto() {
// destructor
cout << "Objeto (" << Abcisa << ", " << Ordenada << ") destruido!" <<
endl;
delete [] coord;
}
Punto(const Punto& ref) {
// construtor-copia
coord = new int[2];
Abcisa = ref.Abcisa; Ordenada = ref.Ordenada;
cout << "Copiado objeto: X == "
<< Abcisa << "; Y == " << Ordenada << endl;
}
};
int main() {
Punto p1;
Punto p2(3, 4);
Punto p3 = p2;
return 0;
}
//
//
//
//
//
======================
L.25: p1 instancia de Punto
L.26: p2 instancia de Punto
L.27: p3 instancia de Punto
L.28: fin del programa
Salida:
Creado objeto: X == 1; Y == 2
Creado objeto: X == 3; Y == 4
Copiado objeto: X == 3; Y == 4
Objeto (3, 4) destruido!
Objeto (3, 4) destruido!
Objeto (1, 2) destruido!
Comentario
Tenga en cuenta los "defines" Abcisa y Ordenada, sustituyndolos mentalmente por los valores
respectivos.
En L.25 se instancia el objeto p1; se invoca el constructor por defecto y se utilizan los argumentos
por defecto.
En L.26 se instancia el objeto p2; en este caso se realiza una invocacin implcita del constructor
pasndole dos argumentos.
En L.27 se instancia el objeto p3; en esta ocasin se produce una invocacin implcita del
constructor copia
Al alcanzarse en L.28 el final del programa, son invocados automticamente los destructores de los
objetos creados en el mbito de la funcinmain. En este momento se invocan los destructores de
los objetos p3, p2 y p1 (en orden inverso a su creacin).
Ver otro ejemplo de la necesidad y utilizacin del constructor-copia (
4.9.18d1).
c);
// operador de asignacin
Como puede verse, las copias miembro-a-miembro, ya sean originadas por un constructor-copia o
por el operador de asignacin, no son siempre deseables. Incluso pueden resultar peligrosas, en
especial cuando en los constructores se utilizan punteros y/o objetos persistentes creados connew.
Tema relacionado:
4.9.2a).
2 Errores en el Constructor
2.1 Constructor inadecuado
class C {
...
char* string;
C(char*);
};
// constructor explcito
C::C(char* st) {
// definicin
...
strcpy(string, st);
// L1:
}
Se pretende que la clase acepte sentencias en las que el miembro string pueda ser iniciado con
una cadena de caracteres constantes estilo C mediante sentencias como:
C c1("Contiene el path-name de un fichero");
El programa compila sin dificultad pero genera un error de runtime; concretamente el
consabido: Este programa ha ejecutado una operacin no vlida y ser
interrumpido....
El problema aqu es que nos hemos pasado de listos. Hemos pretendido copiar el "contenido" de la
cadena en lugar del puntero (nuestro miembro es un puntero). El diseo correcto en este caso
sera:
class C {
...
char* string;
C(char*);
};
// constructor explcito
C::C(char* st) {
...
string = st;
}
// definicin
// L1:
Sin embargo este diseo tiende a presentar problemas, ya que la informacin (en este caso una
cadena alfanumrica) est fuera del cuerpo del objeto y por tanto fuera de su control. Es mejor un
diseo alternativo en el que los datos pertenezcan al cuerpo de la clase:
#define LSTRING 250
class C {
...
char string[LSTRING];
C(char*);
// constructor explcito
};
C::C(char* st) {
// definicin
...
strcpy(string, st);
// L1:
}
Esta forma tiene el pequeo inconveniente de que debemos prever de antemano el tamao
mximo LSTRING, que debemos reservar para la matriz, y que la cadena de inicio suministrada no
desbordar el tamao de nuestro buffer si pretendemos itroducir una cadena demasiado larga (en
principio el usuario de nuestro programa quizs no est advertido de este lmite interno). Esto es
fcil insertando una sentencia de control en el constructor:
C::C(char* st) {
// definicin
...
if (strlen(st) < LSTRING)
strcpy(string, st);
// L1:
else
// medidas de control
}
Esta ltima forma tiene la ventaja de que nuestra aplicacin no ser susceptible de un ataque de
desbordamiento de buffer.
3 Errores en el Constructor-copia
3.1 Constructor-copia inadecuado
class C {
...
X* xptr;
C(C&);
// constructor-copia
};
C::C(C& c1) {
xptr = c1.xptr;
// L1:
}
Cuando la clase contiene miembros que son punteros, hay que meditar muy detenidamente si al
copiar un objeto, los punteros del nuevo deben apuntar al mismo objeto que en el original o no. La
omisin o la inclusin de una sentencia como L1 supone que los punteros de ambos objetos
sealan a la misma entidad, lo que puede derivar en innumerables problemas.
3.2
class C {
...
char* string;
C(C&);
// constructor-copia
};
C::C(C& c1) {
// definicin
...
strcpy(string, c1.string);
// L1:
}
Es una versin del error cometido antes en el constructor explcito (2.1
), pero aplicado al
constructor-copia. El origen; el resultado y la solucin son los mismos que en aquel caso. La
versin correcta seria:
class C {
...
char* string;
C(C&);
// constructor-copia
};
C::C(C& c1) {
// definicin
...
string = c1.string;
// L1:
}
Observe que en este caso podra haberse omitido la sentencia L1, ya que el compilador
proporciona por su cuenta una versin por defecto.
4.9.16)
4.9.16)
4.9.19)
Aunque la explicacin detallada de cada uno est en el apartado que se cita, intentaremos dar aqu
una sinopsis conjunta de su utilizacin, reconociendo de antemano que en este punto tienen razn
sus detractores, la utilizacin es confusa y para terminar de arreglarlo, con algunas excepciones.
int main() {
// ===================
ALPHA::LD = 1.0;
// M.1 Acceso a variable LD del subespacio
cout << "Valor LD = " << ALPHA::LD << endl;
ALPHA::C c1 = ALPHA::C(3);
nbsp; // M.3
cout << "Valor c1.x: " << c1.x << endl;
// M.4
}
Salida:
Valor LD = 1
Valor c1.x: 3
Comentario:
Observe la notacin de acceso al constructor de la clase (funcin-miembro) en L.10, as como en la
sentencia de declaracin del objeto c1 en M.3, incluyendo su invocacin explcita al constructor de
la clase.
Es interesante resaltar como, a pesar de lo que pudiera parecer, el objeto c1 pertenece al mbito
de la funcin main, no al subespacio ALPHA. Esto es lo que permite que pueda utilizarse la
sintaxis de M4. En caso contrario habra sido
cout << "Valor c1.x: " << ALPHA::c1.x << endl;
// M.4bis
2.2 En realidad, la mayora de las veces, este tipo de acceso a miembros de clase solo se utiliza
para la definicin de funciones miembro (como en L.10), ya que las propiedades no tienen
existencia antes de la instanciacin de objetos concretos, por lo que en este contexto, no tendra
sentido una expresin como:
ALPHA::C::x = 10;
2.3 Para acceder a la propiedad x es necesario referirse a una instancia concreta de la clase,
aunque como se ve a continuacin
, en este caso ya no se utiliza el operador de resolucin de
mbito :: sino otro. Sin embargo, en los casos en que estos miembros tienen existencia real fuera
de las instancias concretas (como es el caso de los miembros estticos
4.11.7), la notacin
anterior es perfectamente vlida. Por ejemplo:
#include <iostream>
using namespace std;
namespace ALPHA {
class C {
// definicin de clase (subespacio en
subespacio)
public:
static int x;
// variable esttica
static void fun() {cout << "Soy una funcin esttica" << endl; }
};
int C::x = 10;
// definicin de la variable esttica
}
int main() {
// ===================
cout << "Variable ALPHA::C::x == " << ALPHA::C::x << endl; // L.13: Ok.
ALPHA::C::fun();
// L.14:
Ok.
}
Salida:
Variable ALPHA::C::x == 10
Soy una funcin esttica
#include <iostream.h>
namespace ALPHA {
class C {
public: int x;
};
}
int main () {
//
ALPHA::C c;
//
c::x = 33;
//
cout << "Valor c::x =
}
==============
instancia un objeto de la clase
Error: c no es una clase o nombre de subespacio!!
" << c::x << endl;
// Error!!
En estos casos la sintaxis correcta implica utilizar el selector directo de miembro . como se
muestra en el siguiente ejemplo:
#include <iostream.h>
using namespace std;
namespace ALPHA {
class C {
char c;
public:
C(char ch = 'x') { c = ch; x = 0; }
// Constructor
int x;
char getC() { return c; }
};
}
int main () {
// ===================
ALPHA::C c1;
// instanciar objeto de la clase
cout << "Valor x de c1: " << c1.x << endl;
// L.16:
cout << "Valor c de c1: " << c1.getC() << endl;
// L.17:
}
Salida:
Valor x de c1: 0
Valor c de c1: x
Comentario
Observe como el selector directo permite acceder a cualquier tipo de miembro (pblico), tanto
propiedades (L.16) como invocacin de mtodos (L.17).
3.1 Si el acceso al objeto debe realizarse mediante un puntero, puede utilizarse el procedimiento
general de utilizar el operador de indireccin( 4.9.11a) o bien el ms especfico selector
indirecto anteriormente mencionado.
Ejemplo:
class C {
char c;
public:
// Constructor
Es interesante sealar que los miembros de clase, incluso privados, son accesibles desde el
interior de las funciones miembro de dichas clases ( 4.1.3). La razn hay que buscarla en la
forma en que est diseado el mecanismo C++ de bsqueda de nombres ("Name-lookup
1.2.1).
Considere el siguiente ejemplo:
#include <iostream>
int x = 10;
// L.3:
class CL {
int x, y;
// L.5: privados por defecto
public:
void setx(int i) { x = i; } // L.7:
void sety(int y) { CL::y = y; }
int getxy();
};
int CL::getxy() { return x + y; }
int main() {
// ================
CL c;
c.setx(1);
c.sety(2);
std::cout << "X + Y == " << c.getxy() << std::endl;
return 0;
}
Salida:
X + Y == 3
Comentario
Puede verse que las propiedades x e y (privadas), son directamente accesibles desde el interior de
las funciones miembro setx, sety y getxy, sin que sea necesario ningn tipo de especificador de
acceso; incluso a pesar de que esta ltima funcin se define fuera del cuerpo de la clase. La razn
es que cualquier referencia a una variable en una funcin miembro, provoca que el compilador
compruebe si existe tal variable en ese mbito (el de la clase) antes que en el espacio exterior. De
esta forma, no existe ambigedad en la invocacin a x dentro de la funcin setx de L.7. El
compilador sabe que se refiere al miembro x definido en L.5 y no a la variable del mbito global
definida en L.3.
Observe que la presencia del especificador CL::y en el cuerpo de sety (L.8) se justifica porque la
variable local y oculta al miembro del mismo nombre. Ms adelante, al tratar del puntero this, se
expone otra versin de la sintaxis utilizada en L.8 para distinguir entre la variable local y del
miembro de igual nombre ( 4.11.6).
Nota: precisamente el hecho de que los miembros de clase, incluso privados, sean accesibles
desde el interior de sus funciones miembro, es origen de otra de las ventajas de utilizar
funciones miembro (mtodos) y clases de la POO frente a funciones normales. En efecto, las
primeras suelen necesitar la utilizacin de menor nmero de parmetros que las funciones
estndar, ya que la totalidad de propiedades del objeto a que se refieren, son accesibles
directamente desde el interior del mtodo (sin necesidad de que le sean pasados sus valores
en forma de argumento).
Nota: existe una cierta tradicin en C++ de utilizar maysculas en la inicial de nombres de
clases.
3 Usar typedefs
En ocasiones, en vez de asignar un nombre a la clase, se utiliza un typedef ( 3.2.1a), lo que es
exactamente anlogo. Esta sustitucin de nombres de clases por "typedefs" es una prctica muy
frecuente en las clases y estructuras definidas en los ficheros de cabecera de los propios
compiladores.
Aqu existen dos formas posibles de uso: utilizando clases annimas o nominadas (con nombre).
Seran las situaciones siguientes:
typedef class {
...
COMPLEX* cptr;
COMPLEX() { /* ... */ }
~CPMPLEX() { /* ... */ }
// Error!!
// Error!!
// Error!!
} COMPLEX;
// typedef de clase annima
...
COMPLEX c1, *cPtr, arrc[10];
El problema es el ya sealado de que la clase annima no puede contener constructores o
destructores explcitos ni auto-referencias. Por esta razn es muy raro utilizar typdefs sobre clases
annimas. S en cambio sobre estructuras:
typedef struct {
...
COMPLEX* cptr;
// Error!!
} COMPLEX;
// typedef de estructura annima
...
COMPLEX c1, *cPtr, arrc[10];
El uso de un typedef con una estructuras y clases nominadas es la solucin que suele utilizarse en
los ficheros de cabecera de los compiladores. Respondera al siguiente esquema:
typedef class C {
...
C* cptr;
// Ok.
C() { /* ... */ }
~C() { /* ... */ }
// Ok.
// Ok.
} COMPLEX;
// typedef de clase con nombre
...
COMPLEC c1, *cPtr, arrc[10];
Esta definicin permite que los objetos de la clase C puedan contener auto-referencias, as como
constructores y destructores explcitos. Como se ha visto, en ambos casos es posible obtener
instancias de la clase o estructura utilizando el typedef en sustitucin del nombre.
Nota: el ANSI C++ no permite estructuras annimas que no declaren un objeto. Sin embargo,
aunque los compiladores C++ de Borland y Microsoft lo permiten en algunas circunstancias (
4.11.3a Estructuras annimas), recomendamos vivamente seguir el Estndar.
1 Presentacin
El ANSI C++ permite solamente estructuras annimas que declaren un objeto (como en &3
),
pero el compilador Borland C++ permite varios tipos de estructuras annimas que extienden el
ANSI (que no declaren un objeto, como en &2
).
Estas estructuras annimas tienen la siguiente forma:
struct { lista-de-miembros };
&2 Estas estructuras annimas deben ser anidadas, es decir, declaradas dentro de otra clase
(clase, estructura o unin) por ejemplo:
struct my_struct {
int x;
struct {
// estructura annima anidada en my_struct
int i;
};
inline int func1(int y);
} ;
&3 La estructura externa debe estar identificada, es decir, tener un nombre (como en el ejemplo
anterior) o declarar un objeto como en el ejemplo que sigue (tambin pueden tener ambas cosas):
struct {
int x;
struct {
// estructura annima anidada en my_struct
int i;
};
inline int func1(int y);
} S;
&4 Puesto que no existe variable de instancia, la sintaxis C++ para las estructuras annimas no
pueden referenciar el puntero this ( 4.11.6). Por consiguiente, mientras que las estructuras C++
tienen generalmente funciones miembro, las annimas no pueden tenerlas (solo pueden tener
datos). Estos datos (propiedades) pueden ser accedidos directamente, dentro del mbito en que la
estructura ha sido declarada, sin utilizar la sintaxis x.y o p->y.
Ejemplo
#include <iostream>
using namespace std;
struct my_struct {
int x;
struct{
// estructura annima anidada
int i;
};
int func1(int y) {return y + i + x;}
} S;
int main() {
// ================
S.x = 6;
S.i = 4;
// Observe este acceso al miembro i
int y = S.func1(3);
cout << "y == " << y << endl;
return 0;
}
Salida:
y == 13
3 Declaracin adelantada
Una declaracin de clase se dice adelantada o incompleta cuando no se hace la correspondiente
definicin como en el ejemplo:
class X;
// An no tiene definicin!
La declaracin adelantada puede ser til porque una declaracin de este tipo ya permite definir
ciertas referencias a la clase declarada (generalmente referencias a punteros-a-objetos de la
clase), antes que la misma haya sido completamente definida. Ejemplo:
struct X;
// declaracin incompleta
4.11.2a1) de
En todos los casos la entidad de declaracin adelantada debe ser completamente definida antes
de poder utilizarla para otros usos que requieran conocer el tamao del objeto. Ejemplo:
class B;
// declaracin adelantada de B
class C;
// declaracin adelantada de C
class A {
...
friend B;
// Ok.
B b1;
// Error! clase B no definida an
friend C::operator* (A&, C&);
// Ok!.
};
...
class B {...};
class C {...};
3.2 typedef (
// Ilegal!!
Para ms informacin ver "Declaracin de miembros". Ver tambin las declaraciones adelantadas
para las clases VCL.
2 Declaracin de clase
El primero, declaracin de clase, es simplemente asignarle un nombre; una sentencia que
establece la conexin entre el identificador y el objeto al que representa (en este caso una clase).
La declaracin asocia el nombre con un tipo de dato, lo que supone definir como se usa, que
operaciones son permitidas y que sentido tienen estas operaciones [6]. La declaracin sera algo
as:
class Hotel;
Una declaracin de este tipo, sin definicin, se denomina adelantada (
4.11.4a).
3 Definicin de clase
La definicin de clase es el proceso de definir cuales sern sus propiedades y mtodos; proceso
que crea un nuevo tipo [2]. Lo mismo que con las variables normales, con frecuencia la declaracin
y definicin de una clase ocurren simultneamente en la misma sentencia (a menos que se trate de
una declaracin adelantada
).
La definicin de la clase puede ser un proceso muy simple (caso de la herencia simple o mltiple).
Ejemplo:
class Hotel: public Pension, Residencia {};
4 El objeto-clase
Cuando la clase est declarada y definida, termina el trabajo del programador. A partir de aqu, el
compilador traslada dicha declaracin a unafuncin-clase, que es la forma en que existe la clase
en el ejecutable. Ms tarde, en tiempo de ejecucin, la funcin-clase crea un objeto-claseque
representa desde este instante a la clase en cuestin. Existe un solo objeto-clase de cada clase y
solo l puede crear instancias de dicha clase. Como se ha sealado, este objeto-clase tiene sus
propias variables y mtodos (propiedades de clase y mtodos de clase). Una variable
(propiedad) declarada como "de clase" existe una sola vez en cada clase y es similar a una
variable definida como esttica de fichero en la programacin clsica. Por su parte, como veremos
inmediatamente
, los mtodos son por definicin, y por lgica [4], "de clase". Un mtodo de
clase solo puede ser ejecutado por un objeto (instancia) de dicha clase.
Por definicin un objeto-clase tiene como mnimo cuatro mtodos de clase: un constructor por
defecto; un destructor; un constructor-copia y una funcin-operador de asignacin operator=(). En
caso que el programador no los haya definido de forma exsplcita, son proporcionados por el
compilador.
5 Las funciones-miembro
Al llegar a este punto es preciso hacer una observacin de la mayor trascendencia: cuando se
instancia una clase, se crea un objeto que contiene un subconjunto particular de todas las variables
(no estticas
4.11.7) de la clase a que pertenece. Pero aunque coloquialmente se dice que el
objeto tambin "tiene" los mtodos de la clase, en realidad esto no es cierto. El objeto no contiene
una copia de todos sus mtodos, lo que supondra una repeticin innecesaria del mismo cdigo en
todos los objetos de la clase. Los mtodos solo existen en el objeto-clase descrito anteriormente.
En las instancias concretas solo hay una tabla de direcciones (denominada vtable) a los mtodos
de la clase, y el acceso a dichas funciones se realiza a travs de esta tabla de punteros [1]. En el
apartado dedicado al puntero this (
4.11.6) se ampla informacin sobre esta importante
cuestin terica y sus implicaciones prcticas.
6 El resumen del proceso hasta aqu descrito puede ser sintetizado como sigue:
Programador
Compilador
Ejecucin
Ejecucin
Hotel playa;
// 7a
declara playa como perteneciente a la clase Hotel; esta sentencia relaciona el identificador con
un tipo especfico de objeto (de la clase Hotel[3]), del mismo modo que la sentencia int
x; relaciona el identificador x con el tipo de los enteros. Una vez tenemos un objeto, habra que
inicializarlo (aqu se dice construirlo), asignndole espacio en memoria e iniciando en su caso sus
variables [5].
En el caso del entero, aunque la expresin int x; no es formalmente una definicin, en realidad
contiene todo lo que el compilador necesita saber sobre ella para poder asignarle un espacio en
memoria, aunque inicialmente este espacio pueda contener basura si no ha sido inicializado a
ningn valor concreto (
4.1.2).
En el caso del objeto playa, en realidad el compilador toma la sentencia 7a
como una
declaracin mas una definicin. Siempre que se crea un objeto se realiza una llamada implcita o
explcita a un constructor que se encarga de inicializar los miembros del objeto. En el caso de 7a,
adems de asociar el identificador playa con el tipo Hotel, el compilador incluye una invocacin
al constructor por defecto de la clase ( 4.11.2d1), que se encarga a su vez de inicializar el
nuevo objeto correctamente (ver
4.11.2d3 para una ms detallada exposicin del proceso de
inicializacin de los miembros de los objetos).
El resultado es que a partir de dicha declaracin-definicin del objeto, ya podemos utilizarlo
directamente. Por ejemplo, en este caso, utilizando uno de sus mtodos pblicos getroom():
playa.getroom(37);
Por supuesto pueden definirse ms objetos del mismo tipo (instancias de la clase) y objetos
derivados del nuevo tipo, como punteros-a, referencias-a, matrices-de, etc.)
class X { ... };
// define la clase X
X x, &xr, *xptr, xarray[10]; /* instancia 4 objetos derivados de X:
tipo X, referencia-a-X, puntero-a-X y matriz-de-X */
Incluso pueden crearse otros objetos de la misma clase por copia del existente por ejemplo:
X y = x;
Aqu el objeto y se crea por copia del objeto x ya existente. En este caso, se invoca un tipo
especial de constructor, el constructor copia ( 4.11.2d4).
8 Destruccin
Finalmente debemos aadir que cuando el objeto sale definitivamente de mbito, es destruido. De
esta tarea se encarga un operador especial, que tiene la forma de un mtodo-de-clase, que se
encarga limpiar los miembros del objeto y de liberar los recursos asignados inicialmente (como
mnimo, espacio de memoria) antes que el propio objeto se auto-destruya
(destructores
4.11.2d2).
1 Sinopsis
Hemos dicho anteriormente ( 4.11.2b) que cada instancia de una clase tiene su propio juego
de variables; propiedades privativas o heredadas (segn el caso), y que unas y otras se
direccionan del mismo modo. Sin embargo, aunque esto es cierto para las propiedades
no estticas ( 4.11.7); no lo es para los mtodos. Existe una sola versin de los mtodos de
clase en el objeto-clase ( 4.11.5), que solo pueden ser invocados por los objetos de la clase
(podramos decir que son funciones de uso restringido). Pero entonces surge una cuestin: cuando
un objeto invoca uno de estos mtodos Cmo sabe la funcin sobre que instancia de la clase
debe operar?. O dicho con otras palabras: Que juego de variables debe utilizar ?.
Para fijar ideas consideremos el caso del ejemplo siguiente:
#include <iostream>
using namespace std;
class X {
public:
int x;
void pow2() {cout << "El cuadrado es: " << x*x << endl; }
};
void main() {
X x1;
x1.x = 2;
X x2;
x2.x = 5;
x1.pow2();
x2.pow2();
}
// =========
// x1 una instancia de X
// x2 otra instancia de X
// M.5: invocacin de func desde x1
// M.6: invocacin de func desde x2
Salida:
El cuadrado es: 4
El cuadrado es: 25
Observando la salida se hace evidente que en uno y otro caso, la invocacin a pow2 se ha
realizado correctamente (aunque no se le hayan pasado parmetros !!). El compilador sabe sobre
que instancia debe operar y ha utilizado el juego de variables correspondiente.
Recuerde que aunque coloquialmente se pueda decir que x2.pow2() es la "Invocacin del
mtodo pow2() del objeto x2", en el fondo es incorrecto. No existe tal pow2() del objeto x2. Es
ms cercano a la realidad decir: "es la invocacin del mtodo pow2() de la clase X utilizando el
juego de variables del objeto x2".
2 El argumento oculto
La respuesta a como las funciones miembro operan con el conjunto de variables de los objetos
para los que son invocadas, est en que C++ incluye en tales funciones (como pow2) un parmetro
especial oculto denominado this (es una palabra clave C++). this es un puntero al objeto
invocante [2]. Este puntero es pasado automticamente por el compilador como argumento en
todas las llamadas a funciones miembro (no estticas). Como su inclusin es automtica y
transparente para el programador, es frecuente referirse a l como argumento implcito u oculto.
El resultado es que cuando el compilador encuentra una invocacin (con o sin argumentos) del
tipo x.pow2(), calcula la direccin del objeto (&x), y realiza una invocacin del tipo pow2(&x),
utilizando esta direccin como valor del argumento oculto this. Por supuesto, el compilador aade
por su cuenta el argumento correspondiente en la definicin de la funcin X::pow2().
Nota: en realidad, el objeto x de una clase C tiene existencia en memoria en forma de una
estructura que contiene los miembros no estticos de la clase. La direccin del objeto es la del
primer miembro de esta estructura. Las direcciones del resto de miembros se consideran
desplazamientos respecto a esta direccin inicial. Esta arquitectura es la clave del
funcionamiento de los punteros-a-clases ( 4.2.1f) y punteros-a-miembros ( 4.2.1g).
class X {
public:
int x;
void pow2(X* this) {
cout << "El cuadrado es: " << this->x * this->x << endl;
}
};
y a su vez, las invocaciones de M.5 y M.6 se sustituyen por:
X* xptr1 = &x1;
X* xptr2 = &x2;
X::pow2(xptr1);
X::pow2(xptr2);
//
//
//
//
puntero-a-tipoX sealando a x1
puntero-a-tipoX sealando a x2
invocacin de X::func referida a x1
invocacin de X::func referida a x2
Tenga en cuenta que estas lneas solo tratan de ofrecer una imagen didctica, ya que this es
un puntero muy especial que no puede ser declarado explcitamente, por lo que la definicin void
pow2(X* this) { /* ... */ } no sera vlida. Tampoco puede tomarse su direccin o ser
utilizado como Rvalue para una asignacin del tipo this = x (si puede en cambio ser utilizado
como Lvalue). Por otra parte, las invocaciones en la forma X::pow2(xptr); tampoco son
correctas. La forma sintcticamente correcta ms parecida a la imagen que queremos transmitir
sera la siguiente:
#include <iostream>
using namespace std;
class X {
public:
int x;
void pow2(X* xpt) {
cout << "El cuadrado es: " << xpt->x * xpt->x << endl;
}
};
void main() {
X x1;
x1.x = 2;
X x2;
x2.x = 5;
X* xpt1 = &x1;
X* xpt2 = &x2;
x1.pow2(xpt1);
x2.pow2(xpt2);
}
// ======================
// x1 una instancia de X
// x2 otra instancia de X
// puntero-a-tipoX sealando a x1
// puntero-a-tipoX sealando a x2
El problema de identificacin antes sealado no existe para las funciones miembro estticas;
veremos ( 4.11.7) que no tienen puntero this, por lo que este tipo de funciones pueden ser
invocadas sin referirse a ningn objeto particular. La contrapartida es que un mtodo esttico no
puede acceder a miembros no estticos sin utilizar los selectores de miembro . o -> ( 4.11.2e).
Es decir, hay que indicarle explcitamente sobre que juego de variables debe operar.
Las funciones friend (
4.11.2a1) que no son miembros de ninguna clase tampoco disponen de
este argumento oculto (no disponen de punterothis). Tampoco las estructuras annimas (
4.11.3a)
4 En el siguiente ejemplo se definen dos clases, idnticas salvo en la forma de referenciar a sus
miembros; en una se utiliza el puntero this de forma explcita, en otra de forma implcita, ambas
son equivalentes, aunque es ms normal utilizar la forma implcita.
#include <iostream.h>
class X {
int x;
public:
int getx() { return x; }
void putx (int i) { x = i; }
};
class Y {
int x;
public:
int getx() { return this->x; }
void putx (int i) { this->x = i; }
};
void main() {
// ==========
X x1;
x1.putx(10);
cout << "Valor de x1.x: " << x1.getx() << endl;
Y y1;
y1.putx(20);
cout << "Valor de y1.x: " << y1.getx() << endl;
}
Salida:
Valor de x1.x: 10
Valor de y1.x: 20
5 A continuacin se expone una variacin del ejemplo presentado al tratar del acceso a
miembros de clases desde funciones miembro ( 4.11.2e). En esta ocasin se utiliza el
puntero this en vez del operador de acceso a mbito.
#include <iostream.h>
int x = 10;
// L.3:
class CL {
int x, y;
// L.5: privados por defecto
public:
void setx(int i) { x = i; } // L.7:
void sety(int y) { this->y = y; }
int getxy();
};
int CL::getxy() { return x + y; }
int main() {
// =============
CL c;
c.setx(1);
c.sety(2);
cout << "X + Y == " << c.getxy() << endl;
return 0;
}
Salida:
X + Y == 3
Comentario
En esta ocasin la presencia explcita del puntero this en el cuerpo de sety (L.8) se justifica
porque la variable local y oculta al miembro del mismo nombre.
Puesto que, segn hemos declarado
, el puntero this referencia al objeto invocante, la
expresin *this representa al objeto, por lo que suele utilizarse cuando se quiere devolver el objeto
invocado por la funcin-miembro.
derecho de la asignacin. Posteriormente este valor es asignado al objeto x2. Este es el resultado
que se obtiene en la tercera salida.
7 La expresin *this tambin puede ser utilizada para devolver una referencia (
objeto invocado por la funcin-miembro. El ejemplo puede modificarse para hacer
que pow2 devuelva una referencia al objeto:
4.2.3) al
#include <iostream>
using namespace std;
class X {
public:
int x;
X& pow2() {
x = x * x;
return *this;
}
};
void main() {
// ==============
X x1, x2;
// instancias de X
x1.x = 2; x2.x = 5;
x1.pow2();
// invocacin de func desde x1
x2.pow2();
// invocacin de func desde x2
cout << "x1 = " << x1.x << endl;
cout << "x2 = " << x2.x << endl;
x2.pow2() = x1.pow2();
// M.7:
cout << "x2 = " << x2.x << endl;
}
Salida:
x1 = 4
x2 = 25
x2 = 16
Comentario
Las salidas son las mismas que en el ejemplo anterior, aunque con una diferencia significativa: la
referencia devuelta por la invocacin pow2 sobre el objeto x1 (en el lado derecho de la asignacin
M.7), es utilizado como Rvalue de la asignacin, y aplicada a la referencia devuelta por la
asignacin del lado izquierdo (que es ahora un Lvalue).
Esta capacidad de las referencias: ser un Rvalue cuando se utilizan a la derecha de una
asignacin y un Lvalue, cuando se sitan a la izquierda, es precisamente la razn por la que se
introdujeron en el lenguaje, siendo una propiedad muy utilizada en la sobrecarga de las funcionesoperador. En el captulo dedicado a la sobrecarga del operador preincremento se expone en
detalle esta caracterstica (
4.9.18c).
4.9.18.e y
4.9.18.e;
2 Propiedades estticas
El punto importante para comprender el porqu y el cmo de los miembros estticos, es conocer
que con ellos ocurre algo parecido que con las funciones-miembro ( 4.11.5). Aunque
convencionalmente se acepta que los objetos contienen un sub-conjunto de "todas" las
propiedades de la clase, esto solo es cierto para las variables no-estticas. Las propiedades
estticas se comportan aqu de forma parecida a las variables estticas de funciones normales.
Solo existe una copia de ellas, que en realidad est en el objeto-clase ( 4.11.5). Se trata por
tanto de propiedades de clase. A cada instancia le basta un puntero a los valores del objeto-clase y
cuando desde una instancia cualquiera accedemos a una de estas propiedades, en realidad se
accede a esta nica copia.
Lo ponemos de manifiesto con un sencillo experimento:
#include <iostream.h>
class C { public: static int x; };
int C::x = 13;
// L.3: definicin
int main () {
C c1, c2;
cout << "Valor
cout << "Valor
c1.x = 22;
cout << "Valor
cout << "Valor
}
// ================
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;
Salida:
Valor
Valor
Valor
Valor
c1.x
c2.x
c1.x
c2.x
==
==
==
==
13
13
22
22
En el ejemplo se han instanciado dos objetos, c1 y c2. El valor inicial 13 asignado a la variable
esttica x en L.3 (volveremos de inmediato a esta "extraa" sentencia
), es puesto de manifiesto
en las dos primeras salidas. A continuacin vemos como la modificacin del valor x en el
objetoc1 tiene la virtualidad de cambiar dicha propiedad x en el segundo objeto.
Observe que definiciones como la de L.3 del ejemplo anterior, solo son permitidas para
miembros estticos y son posibles con independenciade que sean pblicos o privados es decir:
class C {
static char*
public:
static char*
char* ptr3;
};
char* C::ptr1 =
char* C::ptr2 =
char* C::ptr3 =
ptr1;
ptr2;
// declaracin
"Adios";
"mundo";
"cruel";
// Ok. Definicin
// Ok
// Error !! ptr3 no es esttica
Lo anterior no significa que las propiedades estticas, privadas o protegidas, puedan ser accedidas
directamente desde el exterior es decir:
...
func () {
...
cout << "Valor de ptr1: " << C::ptr1 << endl;
accesible!
cout << "Valor de ptr2: " << C::ptr2 << endl;
}
// Error: no
// Ok: -> "mundo"
De todo esto se derivan algunas consecuencias tericas y prcticas. La primera importante es que
al existir en el objeto-clase, las propiedades estticas no dependen de ninguna instancia para su
existencia. Es decir, existen incluso antes que ninguna instancia de la clase. Esto ha sido ya
puesto de manifiesto en la lnea 3 del ejemplo anterior
, donde hemos iniciado la variable
esttica x antes que se haya instanciado ningn objeto de la clase.
Las asignaciones de las tres ltimas lneas proporcionan espacio de almacenamiento, a la vez que
pueden servir de inicializadores de los miembros correspondientes. Observe especialmente la
notacin empleada. Los especificadores de tipo: int, int* y char* son necesarios. En cambio, no es
preciso repetir aqu la palabra static.
Nota: esta inicializacin de las constantes estticas fuera del cuerpo de la clase, es una
excepcin de la regla general C++ de que las propiedades de clases solo pueden inicializarse
en el cuerpo del constructor o en la lista de inicializadores (
4.11.2d3). La razn es que los
miembros estticos no son parte de los objetos de la clase sino objetos independientes [3].
A este respecto tenga en cuenta que los miembros estticos de una clase global pueden ser
inicializados como objetos globales ordinarios, pero solo dentro del mbito del fichero.
Es oportuno sealar que la inclusin de un constructor explcito en la declaracin de la
clase no hubiese evitado tener que incluir las tres ltimas sentencias para definicin de los
miembros respectivos. Por ejemplo, el cdigo que sigue dara los mismos errores de compilacin
que el anterior.
class C {
static y;
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
C () {
// constructor por defecto
y = 1;
p = &y;
c = "ABC";
};
La razn es evidente: el constructor es invocado cuando se instancia un miembro de la clase,
mientras que los miembros estticos (que en realidad "pertenecen" a la clase y no a las instancias),
tienen existencia incluso antes de existir ninguna instancia concreta
.
Como consecuencia directa, si se incluye una asignacin a un miembro esttico dentro del
constructor, al ser esta asignacin posterior a la que se realiza en la definicin, los valores
indicados en el constructor machacarn a los que existieran en la definicin.
Ejemplo
#include <iostream>
using namespace std;
class A {a
public: static int x;
// miembro esttico
A(int i = 12) { x = i; }
// constructor por defecto
};
int A::x = 13;
// definicin de miembro
int main() {
//
cout << "Valor de A.x: "
A a1;
//
cout << "Valor de A.x: "
return 0;
}
Salida:
Valor de A.x: 13
Valor de A.x: 12
==============
<< A::x << endl;
Invoca al constructor.
<< A::x << endl;
Para verificar la sucesin de los hechos, construimos otro sencillo experimento, aadiendo algunas
instrucciones al ejemplo anterior:
#include <iostream.h>
class C {
static y;
// int por defecto
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
C () {
// constructor por defecto
y = 1;
// iniciadores de "instancia"
x = 3;
p = &y;
c = "ABC";
}
};
int C::y = 20;
// iniciadores de "clase"
int* C::p = &C::y;
char* C::c = "abc";
int main () {
cout << "Valor
cout << "Valor
cout << "Valor
/*cout << "Valor
Member C::x
C c1;
cout << "Valor
cout << "Valor
cout << "Valor
cout << "Valor
}
// ===============
.y == " << C::gety() << endl;
.p == " << *(C::p) << endl;
.c == " << C::c << endl;
.x == " << C::x << endl; ERROR:
cannot be used without an object in function main() */
c1.y
c1.p
c1.c
c1.x
==
==
==
==
"
"
"
"
<<
<<
<<
<<
Salida:
Valor
Valor
Valor
Valor
Valor
Valor
Valor
.y == 20
.p == 20
.c == abc
c1.y == 1
c1.p == 1
c1.c == ABC
c1.x == 3
095