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.2a  Construcción de una clase

§1  Sintaxis

La construcción de una clase partiendo desde cero, es decir, cuando no deriva de una clase previa, tiene la siguiente sintaxis (que es un caso particular de la sintaxis general 4.11.2):

class-key <info> nomb-clase { <lista-miembros> };

Ejemplo

class Hotel { int habitd;  int habits; char stars[5]; };

Es significativo que la declaración (y definición) de una clase puede efectuarse en cualquier punto del programa, incluso en el cuerpo de otra clase (§2.5 ).

Salvo que se trate de una declaración adelantada ( 4.11.4), el bloque <lista-miembros>, también denominado cuerpo de la clase, debe existir, y declarar en su interior los miembros que constituirán la nueva clase, incluyendo especificadores de acceso (explícitos o por defecto) que especifican aspectos de la accesibilidad actual y futura (en los descendientes) de los miembros de la clase.

Nota: la cuestión de la accesibilidad de los miembros está estrechamente relacionada con la herencia, por lo que hemos preferido trasladar la explicación de esta importante propiedad al capítulo dedicado a la herencia ( 4.11.2b).

§2  Quién puede ser miembro?

La lista de miembros es una secuencia de declaraciones de propiedades de cualquier tipo; incluyendo enumeraciones; campos de bits etc. Así como declaración y definición de métodos; todos ellos con especificadores opcionales de acceso y de tipo de almacenamiento. auto ( 4.1.8a), extern ( 4.1.8d) y register ( 4.1.8b) no son permitidos; sí en cambio static (4.11.7) y const ( 3.2.1c). Los elementos así definidos se denominan miembros de la clase. Hemos dicho que son de dos tipo:  propiedades de clase (datos) y métodos de clase (algoritmos para manejar los datos).

Es importante advertir que los elementos constitutivos de la clase deben estar completamente definidos para el compilador en el momento de su utilización. Esta advertencia solo tiene sentido cuando se refiere a utilización de tipos abstractos como miembros de clases, ya que los tipos simples (preconstruidos en el lenguaje) quedan perfectamente definidos con su declaración ( 4.1.2). Ver a continuación una aclaración sobre este punto (§2.6 ).

Ejemplo de definición de una clase:

class Vuelo {                 // Vuelo es la clase
  char nombre[30];            // nombre es una propiedad
  int capacidad;
  enum modelo {B747, DC10};
  char origen[8];
  char destino[8];
  char fecha[8];
  void despegue(&operacion};  // despegue es un método
  void crucero(&operacion);
};


§2.1
  Los miembros pueden ser de cualquier tipo con una excepción: no pueden ser la misma clase que se está definiendo (lo que daría lugar a una definición circular), por ejemplo:

class Vuelo {
    char nombre[30];
    class Vuelo;    // Ilegal
    ...
};

§2.2  Sin embargo, sí es lícito que un miembro sea puntero ( 4.2.1f) al tipo de la propia clase que se está declarando:

class Vuelo {
    char nombre[30];
    Vuelo* ptr;
    ...
};

En la práctica esto significa que un miembro ptr de un objeto c1 de una clase C, es un puntero que puede señalar a otro objeto c2 de la misma clase.

Nota: esta posibilidad es muy utilizada, pues permite construir árboles y listas de objetos (unos enlazan con otros).  Precisamente en el capítulo dedicado a las estructuras auto referenciadas ( 4.5.8), se muestra la construcción de un arbol binario utilizando una estructura que tiene dos elementos que son punteros a objetos del tipo de la propia estructura (recuerde que las estructuras C++ son un caso particular de clases 4.5a1).


§2.3  También es lícito que se utilicen referencias ( 4.11.2d4) a la propia clase:

class X {
  int i;
  char c;
  public:
  X(const X& ref, int x = 0); {   // Ok. correcto
    i = ref.i;
    c = ref.c;
};

De hecho, un grupo importante de funciones miembro, los constructores-copia, se caracterizan precisamente por aceptar una referencia a la clase como primer argumento ( 4.11.2d4).


§2.4  Las clases pueden ser miembros de otras clases, clases anidadas. Por ejemplo:

class X {            // clase contenedora (exterior)
   public:
   int x;
   class Xa {        // clase dentro de clase (anidada)
      public:
      int x;
   };
};

Ver aspectos generales en: clases dentro de clases ( 4.13.2).


§2.5
  Las clases pueden ser declaradas dentro de funciones, en cuyo caso se denominan clases locales, aunque presentan algunas limitaciones ( 4.11.2a3). Ejemplo:

void foo() {           // función contenedora
   ...
   int x;
   class C {           // clase local
      public:
      int x;
   };
}


§2.6  También pueden ser miembros las instancias de otras clases (objetos):

class Vertice {
   public: int x, y;
};
class Triangulo {           // Clase contenedora
   public:
   Vertice va, vb, vc;      // Objetos dentro de una clase
};

Es pertinente recordar lo señalado al principio (§2 ): que los miembros de la clase deben ser perfectamente conocidos por el compilador en el momento de su utilización. Por ejemplo:

class Triangulo {
   ...
   Vertice v;      // Error: Vertice no definido
};
class Vertice {...};

En estos casos no es suficiente realizar una declaración adelantada ( 4.11.4) de Vertice:

class Vertice;
class Triangulo {
   public:
   Vertice v;      // Error: Información insuficiente de Vertice
};
class Vertice {...};

ya que el compilador necesita una definición completa del objeto v para insertarlo como miembro de la clase Triangulo.

La consecuencia es que importa el orden de declaración de las clases en el fuente. Debe comenzarse definiendo los tipos más simples (que no tienen dependencia de otros) y seguir en orden creciente de complejidad (clases que dependen de otras clases para su definición). También se colige que deben evitarse definiciones de clases mutuamente dependientes:

class A {
   ...
   B b1;
};
class B {
   ...
   A a1;
};

ya que conducirían a definiciones circulares como las señaladas antes (§2.2 ).


§2.7
  Los miembros de la clase deben ser completamente declarados dentro del cuerpo, sin posibilidad de que puedan se añadidos fuera de él. Las definiciones de las propiedades se efectúan generalmente en los constructores (un tipo de función-miembro) , aunque existen otros recursos (§4 inicialización de miembros ). La definición de los métodos puede realizarse dentro, o fuera del cuerpo (§5 funciones inline ). Ejemplo:

class C {

    int x;

    char c;

    void foo();

};

int C::y;               // Error!! declaración off-line

void C::foo() { ++x; }  // Ok. definición off-line


§2.8 
Las funciones-miembro (métodos), pueden ser declaradas inline (§5 ), static ( 4.11.7), virtual ( 4.11.8a), const ( 3.2.1c) y explicit ( 4.11.2d1) si son constructores. Por defecto tienen enlazado externo ( 1.4.4)

§3  Clases vacías

Los miembros pueden faltar completamente, en cuyo caso tendremos una clase vacía.  Ejemplo:

class Empty {};

La clase vacía es una definición completa y sus objetos son de tamaño distinto de cero, por lo que cada una de sus instancias tiene existencia independiente.  Suelen utilizarse como clases-base durante el proceso de desarrollo de aplicaciones. Cuando se sospecha que dos clases pueden tener algo en común, pero de momento no se sabe exactamente qué.

§4  Inicialización de miembros

Lo mismo que ocurre con las estructuras, que a fin de cuentas son un tipo de clase ( 4.5.1), en su declaración solo está permitido señalar tipo y nombre de los miembros, sin que se pueda efectuar ninguna asignación, ni aún en el caso de que se trate de una constante (). Así pues, en el bloque <lista-miembros> no pueden existir asignaciones.  Por ejemplo:

class C {
  ...
  int x = 33;      // Asignación ilegal !!
  ...
};

Las únicas excepciones permitidas son la asignación a constantes estáticas enteras y los enumeradores (ver a continuación), ya que los miembros estáticos ( 4.11.7) tienen unas características muy especiales.  Ejemplo:

class C {
  ...
  static const int kte = 33;     // Ok:
  static const kt1 = 33.0        // Error: No entero
  cont int kt2 = 33;             // Error: No estática
  static kt3 = 33;               // Error: No constante
  static const int kt4 = f(33);  // Error: inicializador no constante
};


El sitio idónea para situar las asignaciones a miembros es en el cuerpo de las funciones de clase (métodos). En especial las asignaciones iniciales (que deben efectuarse al instanciar un objeto de la clase) tienen un sitio específico en el cuerpo de ciertos métodos especiales denominados constructores ( 4.11.2d1). En el epígrafe "Inicializar miembros" ( 4.11.2d3) se ahonda en esta cuestión.


§4.1  Sí es posible utilizar y definir un enumerador (que es una constante simbólica 3.2.3g), dentro de una clase. Por ejemplo:

class C {
  ...
  enum En { E1 = 3, E2 = 1, E3, E4 = 0};
  ...
};


  En ocasiones es posible utilizar un enumerador para no tener que definir una constante estática. ( 4.11.7).

Ejemplo-1  Las tres formas siguientes serían aceptables:

class C {
  static const int k1 = 10;
  char v1[k1];
  enum e {E1 = 10};
  char v2[E1];
  enum {KT = 20};
  char v3[KT];
  ...
};

Ejemplo-2:

class CAboutDlg : public CDialog {
  ...
  enum { IDD = IDD_ABOUTBOX };
  ...
};

La definición de la clase CAboutDlg pertenece a un caso real tomado de MS VC++.  El enumerador anónimo es utilizado aquí como un recurso para inicializar la propiedad IDD con el valor IDD_ABOUTBOX que es a su vez una constante simbólica ( 1.4.1a) para el compilador. De no haberse hecho así, se tendría que haber declarado IDD como constante estática. En cambio, la forma adoptada la convierte en una variable enumerada anónima que solo puede adoptar un valor (otra forma de designar al mismo concepto).

Ejemplo-3:

class C {
  ...
  enum { CERO = 0, UNO = 1, DOS = 2, TRES = 3 };
};
...
void foo(C& c1) {
  std::cout << c1.CERO;     // -> 0
  std::cout << c1.TRES;     // -> 3
}


Téngase en cuenta que las clases son tipos de datos que posteriormente tienen su concreción en objetos determinados. Precisamente una de las razones de ser de las variables de clase, es que pueden adoptar valores distintos en cada instancia concreta. Por esta razón, a excepción de las constantes ( 3.2.1c) y los miembros estáticos ( 4.11.7), no tiene mucho sentido asignar valores a las variables de clase, ya que los valores concretos los reciben las instancias, bien por asignación directa, o a través de los constructores.

En el apartado dedicado a Inicialización de miembros ( 4.11.2d3) volvemos sobre la cuestión, exponiendo con detalle la forma de realizar estas asignaciones, en especial cuando se trata de constantes.

§4.2  Disposición práctica

En proyectos medianos y grandes, es frecuente que las definiciones de clases se coloquen en ficheros de cabecera ( 4.4.1), de forma que sean accesibles a cualquier otro programador (del equipo) que deba utilizarlas. Generalmente estos ficheros tienen el mismo nombre que la clase con la terminación .h. Por ejemplo: MiClase.h, y como en todos los ficheros de este tipo, en su interior deben instalarse las adecuadas directivas de guarda ( 4.9.10e).

§5  Funciones inline

Las funciones miembro deben declararse dentro del cuerpo de la clase; la definición puede hacerse dentro o fuera. En el primer caso, cuando declaración y definición se realizan dentro del cuerpo de la clase, se denominan funciones inline; la razón es que este tipo de métodos se suponen [1] con el especificador inline implícito ( 4.4.6b). Por ejemplo, la definición:

int i;             // global
class X {
   public:
   char* func(void) { return i; } // inline implícito
   char* i;
};

es equivalente a:

int i;                // global
class X {
   public:
   char* func(void);  // prototipo (declaración)
   char* i;
};
inline char* X::func(void) { return i; } // definición


En esta segunda sintaxis, func es definida fuera de la clase con el especificador inline explícito. En ambos casos, el valor i devuelto por func es el puntero a carácter i de la clase, no la variable global del mismo nombre.

Por esta razón, es frecuente que los miembros definidos fuera del cuerpo de la clase se denominen offline, para distinguirlos de los definidos dentro (inline).  Cuando realice este tipo de definiciones (offline) de métodos, no olvide lo indicado respecto a los argumentos por defecto ( 4.4.5).

Nota: algunos autores señalan que para facilitar la comprensión y simplicidad del código, debe evitarse siempre la definición de los métodos dentro del cuerpo de la clase.  En aquellos casos en que sea deseable una sustitución inline, se debe utilizar explícitamente esta directiva, pero sacando siempre la definición fuera del cuerpo de la clase.

Observe que cuando la definición de func se realiza off-line (fuera del cuerpo de la clase), se utiliza el nombre cualificado ( 4.1.11c) del método. Para ello se utiliza el nombre de la clase seguido del especificador de ámbito :: ( 4.9.19) X::func, con objeto que el compilador sepa que se trata de un método de dicha clase y no la declaración de una nueva función.

Nota: en el diseño de las clases, es preferible reservar las funciones inline (explícitas o implícitas) para funciones pequeñas y de uso frecuente. Por ejemplo, las funciones operator ( 4.9.18) que implementan operadores sobrecargados. Esta forma de proceder presenta una doble ventaja: al evitar la sustitución inline, da lugar a ejecutables más pequeños; además, es más fácil la lectura e interpretación de la definición de la clase al sacar de ella el cuerpo de sus funciones (especialmente si son grandes y farragosas).


La cuestión de definir los métodos dentro o fuera del cuerpo de la clase, no solo concierne a la relación tamaño/rendimiento propia de las funciones inline. También se presentan ocasiones hay que diferir la definición de un método de una clase hasta que no se han definido otras entidades, lo que obliga a una definición externa ( Ejemplo).

  Inicio.


[1]  En ocasiones el compilador dispone de opciones para que esto no ocurra automáticamente. Por ejemplo: GNU Cpp dispone de la opción -fno-default-inline, que hace que las funciones miembro no sean consideradas inline por el mero hecho de haber sido definidas dentro del cuerpo de la clase.