Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


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 declaración de una clase; su definición; su instanciación o concreción en un objeto-clase determinado, y la inicialización del objeto (aunque los dos últimos procesos pueden ejecutarse en la misma sentencia).

§2  Declaración de clase

El primero, declaración de clase, es simplemente asignarle un nombre;  una sentencia que establece la conexión entre el identificador y el objeto al que representa (en este caso una clase). La declaración 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 declaración sería algo así:

class Hotel;

  Una declaración de este tipo, sin definición, se denomina adelantada ( 4.11.4a).

§3  Definición de clase

La definición de clase es el proceso de definir cuales serán sus propiedades y métodos; proceso que crea un nuevo tipo [2]. Lo mismo que con las variables normales, con frecuencia la declaración y definición de una clase ocurren simultáneamente en la misma sentencia (a menos que se trate de una declaración adelantada ).

La definición de la clase puede ser un proceso muy simple (caso de la herencia simple o múltiple). Ejemplo:

class Hotel: public Pension, Residencia {};


También puede ser un proceso más 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 declaración a una función-clase, que es la forma en que existe la clase en el ejecutable. Más tarde, en tiempo de ejecución, la función-clase crea un objeto-clase que representa desde este instante a la clase en cuestión. Existe un solo objeto-clase de cada clase y solo él puede crear instancias de dicha clase. Como se ha señalado, este objeto-clase tiene sus propias variables y métodos (propiedades de clase y métodos de clase). Una variable (propiedad) declarada como "de clase" existe una sola vez en cada clase y es similar a una variable definida como estática de fichero en la programación clásica. Por su parte, como veremos inmediatamente , los métodos son por definición, y por lógica [4], "de clase".  Un método de clase solo puede ser ejecutado por un objeto (instancia) de dicha clase.

Por definición un objeto-clase tiene como mínimo cuatro métodos de clase: un constructor por defecto; un destructor; un constructor-copia y una función-operador de asignación operator=(). En caso que el programador no los haya definido de forma exsplícita, 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 más 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 definición completa de la clase, con todos sus miembros, antes de que pueda instanciarse uno de sus objetos. Además, algunos tipos de clases, las denominadas abstractas, no sirven para instanciar objetos directamente, solo para derivar de ellas otras clases en las que se perfilarán detalles concretos, lo que les permitirá ser instanciables ( 4.11.8c).


El término instancia se refiere siempre a un objeto creado por el objeto-clase en tiempo de ejecución y por supuesto, pueden tener propiedades y métodos. En este caso, se denominan formalmente propiedades de instancia y métodos de instancia.

Cada instancia (objeto) creado desde una clase tiene su propio juego de variables independientes y distintas de los demás 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 aparecerán 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 observación de la mayor trascendencia: cuando se instancia una clase, se crea un objeto que contiene un subconjunto particular de todas las variables (no estáticas 4.11.7) de la clase a que pertenece. Pero aunque coloquialmente se dice que el objeto también "tiene" los métodos de la clase, en realidad esto no es cierto. El objeto no contiene una copia de todos sus métodos, lo que supondría una repetición innecesaria del mismo código en todos los objetos de la clase. Los métodos solo existen en el objeto-clase descrito anteriormente. En las instancias concretas solo hay una tabla de direcciones (denominada vtable) a los métodos de la clase, y el acceso a dichas funciones se realiza a través de esta tabla de punteros [1]. En el apartado dedicado al puntero this ( 4.11.6) se amplía información sobre esta importante cuestión teórica y sus implicaciones prácticas.


§6
El resumen del proceso hasta aquí descrito puede ser sintetizado como sigue:

Programador       Declaración & definición de clase   (en programa fuente)

     

Compilador      Función-clase  (en fichero ejecutable)

     

Ejecución        Objeto-clase  (en memoria)

     

Ejecución      Objeto-instancia  (en memoria)


§7
  Siguiendo con el ejemplo anterior , podríamos señalar que la sentencia:

Hotel playa;        // §7a

declara playa como perteneciente a la clase Hotel; esta sentencia relaciona el identificador con un tipo específico 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, habría que inicializarlo (aquí se dice construirlo), asignándole espacio en memoria e iniciando en su caso sus variables [5].

En el caso del entero, aunque la expresión int x; no es formalmente una definición, 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 ningún valor concreto ( 4.1.2).

En el caso del objeto playa, en realidad el compilador toma la sentencia §7a como una declaración mas una definición. Siempre que se crea un objeto se realiza una llamada implícita o explícita a un constructor que se encarga de inicializar los miembros del objeto.  En el caso de §7a, además de asociar el identificador playa con el tipo Hotel, el compilador incluye una invocación 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 más detallada exposición del proceso de inicialización de los miembros de los objetos).

El resultado es que a partir de dicha declaración-definición del objeto, ya podemos utilizarlo directamente. Por ejemplo, en este caso, utilizando uno de sus métodos públicos getroom():

playa.getroom(37); 


Por supuesto pueden definirse más 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  Destrucción

Finalmente debemos añadir que cuando el objeto sale definitivamente de ámbito, es destruido. De esta tarea se encarga un operador especial, que tiene la forma de un método-de-clase, que se encarga limpiar los miembros del objeto y de liberar los recursos asignados inicialmente (como mínimo, espacio de memoria) antes que el propio objeto se auto-destruya (destructores 4.11.2d2).

  Inicio.


[1]  Esta es precisamente la razón por la que se pueda afirmar ( B. Eckel) que el tamaño que ocupan los objetos C++ en memoria es aproximadamente el mismo que ocuparían las estructuras C equivalentes. Aunque evidentemente, estas últimas no incluyen la "funcionalidad" que acompaña a aquellos. En este sentido una clase es simplemente una estructura a la que se ha añadido una v-table.

[2]  Como veremos más adelante, al hablar de clases abstractas y funciones virtuales, estas definiciones pueden ser dejadas muy en el aire, de forma que puedan ser concretadas más tarde. Este "más tarde" significa que la clase que se define no será utilizada (instanciada) como tal, sino a través de clases derivadas; las definiciones se concretan en estas subclases.

[3]  No se olvide que en este sentido, clase es sinónimo de tipo de objeto; cada clase define un tipo nuevo y específico. Este es precisamente uno de los paradigmas de la POO, los tipos de datos son infinitos, solo dependen de la imaginación y necesidad del usuario.

[4]  Lo contrario exigiría de existencia de una copia de cada método en cada objeto, algo a todas luces ilógico y contrario al espíritu de lo que es una función (en cambio existe una copia de cada propiedad en cada objeto).

[5]  Se prefiere el término "construcción" a "inicio" porque el primero tiene un sentido más amplio. Se refiere a conformar el ambiente adecuado para que los métodos de la clase puedan operar sobre el objeto. Esto puede referirse no solo a inicializar las propiedades, también proporcionar determinados recursos, por ejemplo espacio de memoria, un fichero, el bloqueo de un recurso compartido, o el establecimiento de una línea de comunicación. Este tópico puede verse con más detalle en el apartado correspondiente (Constructores 4.11.2d1).

[6]  Recordar que en determinadas circunstancias, la declaración de tipo class, puede ser sustituida por un typename ( 3.2.1e) y que la declaración de tipo estruct, se refiere en realidad, a una clase en la que todos sus miembros son públicos.