Sie sind auf Seite 1von 44

3.

2 Invocacin implcita de destructores


Adems de las posibles invocaciones explcitas, cuando una variable sale del mbito para el que
ha sido declarada, su destructor es invocado de forma implcita. Los destructores de las variables
locales son invocados cuando el bloque en el que han sido declarados deja de estar activo. Por su
parte, los destructores de las variables globales son invocados como parte del procedimiento de
salida (
1.5) despus de la funcin main ( 4.4.4).

3.2.1 En el siguiente ejemplo se muestra claramente como se invoca el destructor cuando un


objeto sale de mbito al terminar el bloque en que ha sido declarado.
#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;
}
// punto de invocacin del destructor de a
return 0;
}
Salida:
Valor de a.x: 1
El destructor ha sido invocado

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
).

4 Propiedades de los destructores


Cuando se tiene un destructor explcito, las sentencias del cuerpo se ejecutan antes que la
destruccin de los miembros. A su vez, la invocacin de los destructores de los miembros se
realiza exactamente en orden inverso en que se realiz la invocacin de los constructores
correspondientes (
4.11.2d1). La destruccin de los miembros estticos se ejecuta despus que
la destruccin de los miembros no estticos.
Los destructores no pueden ser declarados const (
3.2.1c) o volatile (
3.2.1d), aunque
pueden ser invocados desde estos objetos. Tampoco pueden ser declarados static (
4.11.7), lo
que supondra poder invocarlos sin la existencia de un objeto que destruir.

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;
}

// puntero a superclase asignado a objeto de

// Ok: delete es necesario siempre que se usa new

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);
}

// prototipo -observe que son referencias a la

// usamos referencias a la subclase!!

void foo(B& b1, B& b2) {


b1.~B();
// invocacin -virtual- a ~D()
b2.B::~B();
// invocacion esttica a B::~B()
}

5.1 En el siguiente ejemplo se refiere a un caso concreto de la hiptesis anterior. Muestra


como virtual afecta el orden de llamada a los destructores. Sin un destructor virtual en la clase
base, no se producira una invocacin al destructor de la clase derivada.
#include <iostream>
class color {
// clase base
public:
virtual ~color() {
// destructor virtual
std::cout << "Destructor de color" << std::endl;
}
};
class rojo : public color {

// clase derivada (hija)

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).

6 Los destructores y las funciones virtuales


El mecanismo de llamada de funciones virtuales est deshabilitado en los destructores por las
razones ya expuestas al tratar de los constructores (
4.11.2d1)

7 Los destructores y exit


Cuando se invoca exit (
&1.5.1) desde un programa, no son invocados los destructores de
ninguna variable local del mbito actual. Las globales son destruidas en su orden normal.

8 Los destructores y abort


Cuando se invoca la funcin abort (
&1.5.1) en cualquier punto de un programa no se invoca
ningn destructor, ni an para las variables de mbito global.

4.11.2d3 Iniciar miembros (I)


1 Sinopsis
Hemos sealado (
4.11.2d1), que la misin de los constructores es la correcta iniciacin de los
miembros cuando se crean objetos de la clase. Esta iniciacin puede hacerse de varias formas,
que en algunos casos pueden coexistir. Tres de ellas se utilizan para instanciar objetos. La ltima
es un recurso a utilizar en la definicin de la clase.

Asignacin directa al instanciar el objeto


Lista de iniciadores al instanciar el objeto
Invocacin implcita o explcita al constructor con una lista de argumentos al instanciar el
objeto
Lista de Iniciadores en el constructor de la clase (Pg. siguiente
)

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.8: Solo con propiedades estticas!!


//
//
//
//

========
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'};

// invocacin implcita al constructor + asignacin

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;

// Ok. equivalente al anterior

cout << "V1.y = " << v1.y << endl;


cout << "V1.pa.x = " << v1.pa.x << endl;
cout << "V1.pa.y = " << v1.pa.y << endl;
}
Salida:
V1.x = 2
V1.y = 3
V1.pa.x = 4
V1.pa.y = 5

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);
};

// privadas por defecto


// a partir de aqu todos los miembros son pblicos
// Prototipo de Constructor

X::X(int entero, char caracter){ // Definicin del constructor


i = entero;
c = caracter;
};
...

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

5 Paso de argumentos y sentencias de asignacin en el cuerpo del constructor


La iniciacin de miembros puede realizarse mediante asignaciones en el cuerpo del constructor.
Puede disponerse que este acepte determinados argumentos que determinarn los valores de

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;

//
//
//
//
//

declara ptr puntero-a-tipo-X


invocacin explcita a C1 mediante puntero
invocacin explcita a C1
invocacin implcita a C1
invocacin implcita a C1

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.

4.11.2d4 Copiar objetos


1 Prembulo
Al resear los mecanismos generales de creacin y destruccin de objetos (
4.11.2d), indicamos
que cuando se manejan objetos abstractos (instancias de clases), el operador de
asignacin = tiene un sentido por defecto que supone la asignacin miembro a miembro del
operando derecho en el izquierdo. Dicho en otras palabras: cuando se define una clase, el
compilador define automticamente una funcin-operadoroperator=() que realiza una asignacin
miembro-a-miembro de los elementos del operando derecho en el izquierdo. Por ejemplo,
si c1 e c2 son objetos de la clase C, la asignacin
c1 = c2;

// 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) { /*...*/ };

El programador es libre para asignar al constructor-copia el significado que desee. De hecho,


siempre que se respeten las reglas de sobrecarga de funciones, pueden existir mltiples versiones
de estos constructores, cada uno con su propia finalidad. Como puede verse en la tercera
sentencia, el constructor-copia tambin puede tener otros argumentos, pero en cualquier caso, la
referencia a la propia clase debe ser el primero de ellos.
Como sugiere su nombre, el constructor-copia se utiliza para copiar objetos. Sin embargo, el
concepto "copia" puede tomarse con diversos significados en C++: existen copias ligeras y
profundas; tambin copias lgicas ("Shadow copy") y fsicas.

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
}

Cuando se lanza una excepcin (


1.6). Ver
Ejemplo.
Cuando se inicia un objeto; tpicamente cuando se declara e inicia un objeto mediante
asignacin de otro de la misma clase. Es la situacin de las sentencias L.2/3
(ver
Ejemplo):
class C { ....
...
C c1;
defecto
C c2 = c1;
C c3(c1);
c3 = c2;

};
// 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

2.2 El compilador crea un constructor-copia oficial para cualquier clase en la que no se


hubiese definido ningn otro explcitamente (un constructor que pueda ser invocado con un solo
argumento que sea una referencia al propio tipo). Este copiador de oficio realiza un clon exacto del
original replicando todos los miembros a cualquier profundidad, y permite programar de forma
segura con muchos tipos abstractos. Sin embargo, como veremos a continuacin
, cuando el
programa crea tipos abstractos por agregacin de otros tipos complejos como clases, estructuras,
matrices y objetos persistentes que se manejan mediante punteros, puede ser necesario definir
un constructor-copia explcito. Tambin si se sobrecarga el operador de asignacin (3
).
Como todos los constructores, el constructor-copia se define como una funcin que no devuelve
nada (ni siquiera void) y su primer parmetro es una referencia constante a un objeto de la clase
[1]. Puede aceptar ms parmetros; tantos como sean necesarios, siempre que se respeten las
reglas de sobrecarga de funciones, de forma que no exista ambigedad entre dos definiciones. Por
ejemplo:
class X {
int i;
char c;
public:
X(const X&): i(0), c('X') {}
X(const X& ref, int n = 0); {
i = n;
c = ref.c;
};
class Y {
int i;
char c;
public:
Y(const Y&): i(0), c('X') {}
Y(const Y& ref, int n); {
i = n;
c = ref.c;
};

// Error! ambigedad con el anterior

// Ok.

Cuando se define explcitamente un constructor-copia, el compilador no necesita crear ninguna


versin oficial. En tal caso, las sentencias en que se requiere la utilizacin de un constructor-copia
utilizarn la versin explcita. Pero entonces es responsabilidad del programador cubrir todos los
detalles pertinentes. En todos los casos en que, en la definicin de una versin explcita, se omita
especificar la asignacin de una propiedad de la clase, el compilador incluir automticamente una
invocacin al constructor por defecto del miembro. Observe que esto implica que no se realizar
copia de los miembros omitidos, cuyos valores correspondern a objetos de nueva construccin.
Supone tambin que si los miembros omitidos son tipos simples (cuyo constructor por defecto no
los inicializa con ningn valor concreto), los miembros del objeto resultante contendrn basura (
4.11.2d1). La definicin por el programador de un constructor-copia explcito, supone que
este har algo ms que una mera copia de los objetos-miembro de la clase, ya que esto lo hace
por s mismo el constructor-copia oficial; supone as mismo que har algo ms que copiar un
puntero e incrementar un contador, porque eso es justamente lo que hace un shared_ptr.
La pgina adjunta muestra un ejemplo que pone de manifiesto las implicaciones de lo indicado en
prrafo anterior (
Ejemplo)

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).

3 El operador de asignacin oficial


Si en la definicin de la clase no se define ninguna versin explcita de la funcin operator=(), el
compilador proporciona una versin oficial, que puede ser utilizada con miembros de la clase.
Esta versin oficial realiza una copia profunda (miembro a miembro) del operando derecho en el
izquierdo. Es el que permite utilizar expresiones como
c1 = c2;
con objetos de tipo C aunque no se haya definido explcitamente este operador para miembros de
la clase. Su funcionamiento es anlogo al del constructor-copia oficial, aunque en este caso ambos
operandos deben ser objetos creados previamente. Recordaremos que esta funcin tambin
recibe una referencia a un objeto de la clase, aunque se diferencia del constructor-copia en que
devuelve una referencia (los constructores no devuelven nada, simplemente crean un objeto).
C& operator= (const C&

c);

// operador de asignacin

El constructor-copia y el operador de asignacin no tienen porqu coincidir en su diseo. De hecho


suelen existir diferencias substanciales, debido a que el constructor-opia est relacionado con la
creacin de objetos, de forma que implicar la asignacin de recursos (por ejemplo memoria). Por
contra, la asignacin maneja objetos ya construidos, lo que implica la reasignacin de recursos y
eventualmente, desasignacin de los anteriores. En cualquier caso, el mecanismo de copiado
"clnico" de la asignacin oficial presenta los mismos inconvenientes que el constructor-copia
oficial (ver a continuacin 4
), pero el programador tiene libertad para definir su propia versin.
La descripcin del operador de asignacin operator= y la forma de sobrecargarlo, ha sido
discutida con detalle en el apartado correspondiente (
4.9.18a), pero debemos advertir que si se
proporciona una versin explcita, es responsabilidad del usuario cubrir los detalles pertinentes, ya
que solo se efectuarn aquellas asignaciones que sean especificadas de forma explcita.
Los
miembros para los que no exista una asignacin explcita mantendrn sus antiguos valores. La
pgina adjunta incluye un ejemplo que muestra claramente este comportamiento (
Ejemplo).

4 Inconvenientes de la copia miembro-a-miembro


En ocasiones, la rplica miembro a miembro efectuada por el constructor-copia oficial, o por el
operador de asignacin =, presenta riesgo de resultados indeseados cuando algunos miembros
son punteros. Tambin en los procesos de destruccin de tales objetos, cuando son invocados los
destructores al finalizar su mbito. Para ilustrarlo con un ejemplo, supongamos una
clase Cliente para manejar los clientes de un hotel:
class Cliente {
public:
char* nombre;
...
Cliente(char*) {
// constructor
nombre = new char[30];
...
}
~Cliente() {
// destructor
delete[] nombre;
}
};
Si construimos dos instancias de la clase:
void foo() {
Cliente c1( "Pau Casals" );
Cliente c2 = c1;
}
En el primer caso se ha realizado una llamada implcita al constructor con los argumentos
situados en la lista de inicializacin. En el segundo, se ha realizado una llamada implcita
al constructor-copia oficial
.
Cualquiera que sea el mtodo de construccin seguido, resulta evidente que los miembros del
objeto c2 sern exactamente iguales que los de c1. Existe duplicidad de miembros entre ambos
objetos pero la matriz alfanumrica es compartida. Concretamente el puntero nombre de ambas
instancias seala la misma direccin del montn, donde se aloja la cadena "Pau Casals" que fue
creada por c1 (al que consideramos "Propietario" del contenido). Cualquier modificacin
del contenido de esta cadena desde un objeto tiene repercusin en el otro, que "ve" el nuevo
contenido. Pero si c1 cambia totalmente contenido y situacin. Por ejemplo, cambiando el nombre
de cliente actual por otro ms largo, lo que supone rehusar la matriz actual y crear otra de mayor
dimensin en otra zona del montn, el puntero de c2 queda descolgado. Cualquier intento de
acceder al nombre del cliente desde c2 proporcionar basura.
la situacin es an ms conflictiva al finalizar la funcin foo. Ya sabemos ( 4.1.5) que las
variables locales son automticamente destruidas al finalizar su mbito.; En este caso sern
invocados automticamente los destructores de c2 y c1 que destruirn los objetos. Cuando le
llegue el turno a c2, su destructor intentar realizar un delete[] utilizando un puntero invlido, lo
que probablemente producir un error fatal de runtime.

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:

El operador de asignacin con miembros de clases (

4.9.2a).

4.11.2d5 Observaciones y errores ms frecuentes


1 Presentacin
Muchos de los problemas que se presentan en casos prcticos programados en C++ se derivan
del diseo inadecuado de constructores y destructores, en especial algunos de esos errores
inexplicables que pueden acabar con los nervios del ms templado. A continuacin se muestra una
seleccin con algunos de los que considero ms probables, o que he tenido que sufrir en mis
propias carnes.

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.11.2e Acceso a miembros


1 Sinopsis:
El acceso a miembros de clases (propiedades y mtodos), incluyendo el acceso a travs de
punteros, tiene una notacin un tanto especial. En principio C++ dispone de tres operadores
especficos de acceso que suelen ser causa de confusin en el principiante y precisamente uno de
los "reproches" a C++ por parte de sus competidores (C# por ejemplo). Son los siguientes:
:: Operador de resolucin de mbito o acceso a mbito (
.

Selector directo de miembro (

4.9.16)

-> Selector indirecto de miembro (

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.

2 Acceso a miembros de clase


En principio, el operador de resolucin de mbito ::, se utiliza para acceso a miembros de
subespacios y para miembros de clases, lo cual tiene su lgica, habida cuenta que las clases
constituyen en s mismas una especie de subespacios particulares (
4.1.11;
4.1.11c1).
2.1 Ejemplo:
#include <iostream.h>
namespace ALPHA {
long double LD;
class C {
public:
int x;
C(int = 0);
};
}
ALPHA::C::C(int p1) {
x = p1;
}

// declaracin de una variable en el subespacio


// definicin de clase (subespacio en subespacio)
// prototipo de constructor por defecto
// L.10 definicin del constructor por defecto

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

3 Acceso a miembros de objetos


Hemos adelantado que el operador de acceso a mbito :: no puede ser utilizado para acceder
a miembros de objetos (instancias concretas de las clases), ya que los objetos no se consideran
mbitos. Por ejemplo:

#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:

C(char ch = 'x') { c = ch; x = 0; }


int x;
char getC() { return c; }
};
...
C c1, d1;
C* ptrc = &c1;
(*ptrc).x = 10;
indireccin
d.x = (*ptrc).x;
ptrc->x = 10;
d1.x = ptrc->x;
ptrc->getC();
ptrc->C::getC();

// Constructor

// objetos c1 y d1, instancias de la clase C


// define ptrc puntero-a-C sealando a objeto c1
// Acceso a miembro x de c1 mediante operador de
//
//
//
//
//

idem (notacin desaconsejada para miembros)


Ok: Acceso a propiedad mediante selector indirecto
dem (notacin aconsejada para estos casos)
Ok: Acceso a mtodo mediante selector indirecto
Ok: variacin sintctica de la anterior

3.2 Ejemplo de acceso a miembros utilizando ambos selectores.


#include <iostream>
#include <typeinfo.h>
class X {
// clase raz
public: void func1() {std::cout << "func1" << std::endl; }
};
class Y : public X { // Y deriva de X
public: void func2() {std::cout << "func2" << std::endl; }
};
void main() {
// ==========================
X mX;
// mX es instancia de X (clase raz)
mX.func1();
// llamada a func1 de mX
//primera forma de invocacin (selector directo)
Y mY;
// mY es instancia de Y (clase derivada)
mY.func2();
// llamada a funcin miembro (privativa) de mY
mY.func1();
// llamada a funcin miembro de mY (heredada)
//segunda forma de invocacin, mediante puntero (selector indirecto)
Y* ptY = & mY;
// puntero a mY
ptY->func2();
// llamada a funcin miembro (privativa) de mY
ptY->func1();
// llamada a funcin miembro de mY (heredada)
}
Salida:
func1
func2
func1
func2
func1

5 Acceso a miembros de clases desde funciones miembro

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).

6 Recuerde que la invocacin explcita de constructores o destructores de clase, exige una


sintaxis especial ligeramente diferente ( 4.11.2d).

4.11.3 Nombres de clases


1 Objetos annimos
Los nombres de clase deben ser nicos dentro de su mbito. Sin embargo, en determinados casos
pueden omitirse los nombres de uniones, estructuras y clases, dando lugar a elementos sin
nombre (annimos).
Las clases sin nombre pueden usarse para declarar que determinados elementos son instancias
de una determinada clase, pero con el inconveniente de que no pueden declararse instancias
adicionales en ninguna otra parte, y que los miembros de la clase no pueden contener
constructores ni destructores explcitos; tampoco referencias a ella misma (auto-referencias), por
ejemplo, punteros a objetos de la propia clase. Es la situacin siguiente:
class { ... } C1, C2, *pc, arrc[10];

2 Ventaja de nombres explcitos


La sentencia anterior declara C1, C2, *pc y arrc como elementos derivados de la que se define
en el bloque. Sin embargo, no es posible definir nuevos objetos del mismo tipo ms adelante;
tampoco clases derivadas de ella, cosa que no ocurrira si la clase no fuese annima. Por ejemplo:
class X { ... } C1, C2, *pc, arrc[10];
...
X C4;
// nueva instancia de X
class Xb : X { ... }; // nueva clase derivada de X

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.

4.11.3a Estructuras annimas


Nota: caractersticas como las descritas a continuacin, que no son estndar del lenguaje,
sino particularidades de algunos compiladores, son desaconsejables.

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

4.11.4 mbito de nombres de clase


1 Sinopsis
El mbito de los nombres de clase es local. Comienza en el punto de declaracin de la clase y
termina el final del bloque en que se ha declarado. Un nombre de clase oculta a cualquier otra
clase, objeto, enumerador, o funcin del mismo nombre en el mismo mbito, y se exigen ciertas
condiciones especiales si el mismo nombre de clase aparece ms de una vez en el mismo mbito.

2 Si se declara una clase en un mbito que contenga la declaracin de un objeto, funcin, o


enumerador del mismo nombre, la clase solo puede ser referenciada usando un especificador de
tipo, lo que significa que debe utilizarse uno de los especificadores: class, struct o union junto
con el nombre de la clase. Por ejemplo:
struct S { ... };
int S(struct S *Sptr);
void func(void) {
S t;
utiliza
struct S s;
(struct)
S(&s);
}

// declara una clase S de tipo struct


// declara funcin S, homnima con la clase S
// ILEGAL!! la clase S no est visible y no se
// especificador de tipo
// Ok: se utiliza un especificador de tipo
// Ok: llamada a funcin S que es visible

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

struct B { struct X *pa };


struct X { struct B *pb };
La primera ocurrencia de X se llama incompleta porque an no existe definicin en dicho punto.
Este tipo de declaracin se permite en este caso, porque la declaracin de B no precisa del tamao
de X.

3.1 La declaracin adelantada tambin se suele utilizar en la declaracin friend (


clases, cuando esta declaracin es recproca. Ejemplo:
class B;
class A {
...
friend B;
};
...
class B {
...
friend A;
};

4.11.2a1) de

// L.1: declaracin anticipada de B

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 (

3.2.1a) no puede ser utilizado con clases de declaracin adelantada.

typedef struct COMPLEX;

// Ilegal!!

Para ms informacin ver "Declaracin de miembros". Ver tambin las declaraciones adelantadas
para las clases VCL.

4.11.5 Instanciado de clases: Objetos


1 Sinopsis
Existen varios conceptos y fases en la existencia de las entidades de un ejecutable conviene
distinguir: la declaracin de una clase; su definicin; su instanciacin o concrecin en un
objeto-clase determinado, y la inicializacin del objeto (aunque los dos ltimos procesos pueden
ejecutarse en la misma sentencia).

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 {};

Tambin puede ser un proceso ms elaborado:


class Hotel {
char nombre[30];
int room;
public:
int getnom(char *nom);
void putnom(char *nom);
char * getroom(int num);
};

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.

Un objeto-clase no puede ser usado directamente, podemos figurarnos que no es un objeto


concreto. Del mismo modo que para usar un entero hay que declarar uno, con un nombre,
especificando que pertenece a la clase de los enteros y en su caso, iniciarlo a un valor. Para
utilizar un objeto-clase hay que declararlo; en estos caso ms que "declarar" un objeto de la clase
se dice instanciar la clase, que equivale a disponer de un objeto concreto (instancia) de la clase.
Nota: por supuesto es necesario hacer una definicin completa de la clase, con todos sus
miembros, antes de que pueda instanciarse uno de sus objetos. Adems, algunos tipos de
clases, las denominadas abstractas, no sirven para instanciar objetos directamente, solo para
derivar de ellas otras clases en las que se perfilarn detalles concretos, lo que les permitir ser
instanciables (
4.11.8c).

El trmino instancia se refiere siempre a un objeto creado por el objeto-clase en tiempo de


ejecucin y por supuesto, pueden tener propiedades y mtodos. En este caso, se denominan
formalmente propiedades de instancia y mtodos de instancia.
Cada instancia (objeto) creado desde una clase tiene su propio juego de variables independientes
y distintas de los dems objetos hermanos, pero todos pueden acceder (leer/modificar) las
propiedades de clase de su ancestro, el objeto-clase. Puesto que los valores de las variables de
clase del ancestro son nicos, estos aparecern iguales para todos los descendientes, con
independencia cual de sus instancias sea la que acceda a ellas (
4.11.7).

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

Declaracin & definicin de clase (en programa fuente)

Compilador

Funcin-clase (en fichero ejecutable)

Ejecucin

Objeto-clase (en memoria)

Ejecucin

Objeto-instancia (en memoria)

7 Siguiendo con el ejemplo anterior

, podramos sealar que la sentencia:

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).

4.11.6 El puntero this


Nota: los aspectos generales relativos a punteros a clases y a miembros de clase, son
tratados en los epgrafes
4.2.1f y 4.2.1grespectivamente.

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).

En el caso del ejemplo anterior las cosas ocurren "como si"


sido:

la definicin de la clase X hubiese

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)

3 this es una variable local


Como ocurre con todos los parmetros de funciones, resulta que this es una variable
local (puntero) presente en el cuerpo de cualquier funcin miembro no esttica. this no necesita
ser declarado, y es raro que sea referenciado explcitamente en la definicin de alguna funcin, lo
que no es obstculo para que sea utilizado dentro de la propia funcin para referenciar a los
miembros [3].
Segn lo anterior resulta evidente que, como tal variable local, esta palabra clave no puede ser
usada fuera del cuerpo de un mtodo de una clase.
Nota: el conocimiento de los dos puntos mencionados (que es una variable local y que es un
puntero al objeto) son la clave para manejarthis con cierta soltura si las circunstancias lo
requieren. Por ejemplo, si se invoca x.func(y), donde y es un miembro de X, la
variablethis adopta el valor &x e y adopta el valor this->y, lo que equivale a x.y.

En las funciones-miembro no-constantes ( 3.2.1c) de una clase C, el tipo de this es C* (


4.2.1f). Por contra, en los mtodos constantes su tipo es const C* (puntero-a-constante) [1].

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.

6 Considere una variacin sobre el primer ejemplo


, en el que modificamos ligeramente la
definicin del mtodo pow2 haciendo que devuelva un objeto.
#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 pow2 desde x1
x2.pow2();
// invocacin de pow2 desde x2
cout << "x1 = " << x1.x << endl;
cout << "x2 = " << x2.x << endl;
x2 = x1.pow2();
// M.7:
cout << "x2 = " << x2.x << endl;
}
Salida:
x1 = 4
x2 = 25
x2 = 16
Comentario
Comprobamos que las salidas son las mismas que en el primer ejemplo. Las operaciones se han
realizado sobre las variables del objeto correspondiente, pero adems, en este caso el mtodo
devuelve un objeto, por lo que puede ser utilizado como Rvalue de la asignacin M.7. En
consecuencia, el valor 4 del objeto x1 es transformado a 4 * 4 = 16 en la invocacin del lado

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).

Otros ejemplos en:

4.9.18.e y

4.9.18.e;

4.11.7 Miembros estticos


1 Sinopsis
El especificador de tipo de almacenamiento static ( 4.1.8c) puede utilizarse en la declaracin de
propiedades y mtodos de clases. Tales miembros se denominan estticos y tienen distintas
propiedades que el resto. En cada instancia de la clase existe una copia de los miembros noestticos, aunque solo una de estticos para todas las instancias. La singularidad de estas copias
conduce a algunas particularidades no permitidas al resto de los miembros. Por ejemplo, pueden
ser accedidas sin referencia a ninguna instancia concreta de la clase y deben ser definidas como si
fuesen variables estticas normales; adems se permiten algunas singularidades en la notacin de
acceso a estos miembros.

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;

// privado por defecto

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.

3 Definicin de miembros estticos


Puesto que las declaraciones de miembros estticos existentes en la declaracin de una clase no
son definiciones (
4.1.2), y estos miembros existen antes que ninguna instancia de la clase, la
consecuencia es que debe proporcionarse una definicin en algn sitio para proveer de espacio de
almacenamiento e inicializacin (en condiciones normales, esta tarea es encomendada a los
constructores
4.11.2d1). Considere el siguiente ejemplo:
class C {
static y;
// int por defecto en algunos compiladores
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
};

Al compilar se producirn tres errores de enlazado Unresolved external...,


correspondientes a los miembros C::y, C::p y C::c; sealando que las variables estticas estn
declaradas pero no definidas (no tienen espacio de almacenamiento). Para evitarlo, podemos
hacer:
class C {
static y;
public: int x;
static int* p;
static char* c;
static int gety () { return y; }
};
...
int C::y = 1;
// no es necesario poner static (pero si int!!)
int* C::p = &C::y;
// dem int*
char* C::c = "ABC";
// dem char*
...

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].

4 Iniciar miembros estticos


4.1 Iniciar constantes estticas
Recordemos que excepcionalmente, las constantes estticas pueden ser iniciadas en el cuerpo de
la clase ( 4.11.2a). Es decir, se permiten expresiones del tipo [1]:
class C {
static const int k1 = 2;
// Ok:
static const float f1 = 2.0;
// Ok:
static Etiquetas objetos[MAXNUM];
...
};
const int C::k1;
cons float C::f1;
Etiquetas C::cargos[MAXNUM];
Observe que en este caso an es necesario declarar el miembro fuera de la clase, aunque no es
necesaria aqu su inicializacin.
Nota: recordar que es posible sustituir una constante esttica entera por un enumerador (
4.11.2a).

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

==
==
==
==

"
"
"
"

<<
<<
<<
<<

c1.gety() << endl;


*(c1.p) << endl;
c1.c << endl;
c1.x << endl;

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

5 Caractersticas de los miembros estticos


En este sencillo programa comprobamos un buen montn de las caractersticas especiales de los
miembros estticos:
Las tres primeras salidas se producen antes de instanciar ningn objeto:
Los miembros estticos tienen existencia antes que cualquier instancia de la clase.

Los valores iniciales son debidos a los iniciadores de clase.


El intento de construir una cuarta salida siguiendo la pautas de las anteriores con el miembro x noesttico conduce al previsible error de compilacin (muy explcito por cierto).

5.1 Particularidades de la notacin.

095

Das könnte Ihnen auch gefallen