Vous êtes sur la page 1sur 23

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(


indirecto anteriormente mencionado.

4.9.11a) o bien el ms especfico selector

Ejemplo:
class C {
char c;
public:
C(char ch = 'x') { c
int x;
char getC() { return
};
...
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();
//

= ch; x = 0; }

// Constructor

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


Hotel playa;

, podramos sealar que la sentencia:

// 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
}
Salida:

// ================
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;
c1.x == " << c1.x << endl;
c2.x == " << c2.x << endl;

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

==============
<< A::x << endl;
Invoca al constructor.
<< A::x << endl;

Salida:
Valor de A.x: 13
Valor de A.x: 12

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

Vous aimerez peut-être aussi