Curso de C++ (Página 33)

pagina033 Principal pagina034

CAPITULO 34 Modificadores para miembros

Existen varias alternativas a la hora de definir algunos de los miembros de las clases. Esto es lo que veremos en éste capítulo. Estos modificadores afectan al modo en que se genera el código de ciertas funciones y datos, o al modo en que se tratan los valores de retorno.

Funciones en línea (inline):

A menudo nos encontraremos con funciones miembro cuyas definiciones son muy pequeñas. En estos casos suele ser interesante declararlas como inline. Cuando hacemos eso, el código generado para la función cuando el programa se compila, se inserta en el punto donde se invoca a la función, en lugar de hacerlo en otro lugar y hacer una llamada.

Esto nos proporciona una ventaja, el código de estas funciones se ejecuta más rápidamente, ya que se evita usar la pila para pasar parámetros y se evitan las instrucciones de salto y retorno. También tiene un inconveniente: se generará el código de la función tantas veces como ésta se use, con lo que el programa ejecutable final puede ser mucho más grande en ciertas ocasiones.

Es por esos dos motivos por los que sólo se usan funciones inline cuando las funciones son pequeñas. Hay que elegir con cuidado qué funciones declararemos inline y cuales no, ya que el resultado puede ser muy diferente dependiendo de nuestras decisiones.

Hay dos maneras de declarar una función como inline.

La primera ya la hemos visto. Las funciones que se definen dentro de la declaración de la clase son inline implícitamente. Por ejemplo:

class Ejemplo {
  public:
   Ejemplo(int a = 0) { A = a;}

  private:
   int A;
};

En éste ejemplo hemos definido el constructor de la clase Ejemplo dentro de la propia declaración, esto hace que se considere como inline. Cada vez que declaremos un objeto de la clase Ejemplo se insertará el código correspondiente a su constructor.

Si queremos que la clase Ejemplo no tenga un constructor inline deberemos declararla y definirla así:

class Ejemplo {
  public:
   Ejemplo(int a = 0);
 
  private:
   int A;
};
 
Ejemplo::Ejemplo(int a)
{ 
   A = a;
}

En este caso, cada vez que declaremos un objeto de la clase Ejemplo se hará una llamada al constructor y sólo existirá una copia del código del constructor en nuestro programa.

La otra forma de declarar funciones inline es hacerlo explícitamente, usando la palabra reservada inline. En el ejemplo anterior sería así:

class Ejemplo {
  public:
   Ejemplo(int a = 0);
 
  private:
   int A;
};
 
inline Ejemplo::Ejemplo(int a)
{ 
   A = a;
}

Funciones miembro constantes

Esta es una propiedad que nos será muy útil en la depuración de nuestras clases.

Cuando una función miembro no modifique el valor de ningún dato de la clase, podemos y debemos declararla como constante. Esto no evitará que la función modifique los datos del objeto, el código de la función lo escribimos nosotros, pero generará un error durante la compilación si la función intenta modificar alguno de los datos miembro del objeto.

Por ejemplo:

#include <iostream.h>
#include <stdlib.h>
 
class Ejemplo2 {
  public:
   Ejemplo2(int a = 0) {A = a; }
   void Modifica(int a) { A = a; }
   int Lee() const { return A; }
   
  private:
   int A;
};
 
int main()
{
   Ejemplo2 X(6);
 
   cout << X.Lee() << endl;
   X.Modifica(2);
   cout << X.Lee() << endl;
    
   system("PAUSE");
   return 0;
}

Para experimentar, comprueba lo que pasa si cambias la definición de la función "Lee()" por estas otras:

int Lee() const { A++; return A; }
int Lee() const { Modifica(A+1); return A; }
int Lee() const { Modifica(3); return A; }

Verás que el compilador no lo permite.

Miembros estáticos de una clase (Static)

Ciertos miembros de una clase pueden ser declarados como static. Los miembros static tienen algunas propiedades especiales.

En el caso de los datos miembro static sólo existirá una copia que compartirán todos los objetos de la misma clase. Si consultamos el valor de ese dato desde cualquier objeto de esa clase obtendremos siempre el mismo resultado, y si lo modificamos, lo modificaremos para todos los objetos.

Por ejemplo:

#include <iostream.h>
#include <stdlib.h>
 
class Numero {
  public:
   Numero(int v = 0);
   ~Numero();
   
   void Modifica(int v);
   int LeeValor() const { return Valor; }
   int LeeCuenta() const { return Cuenta; }
   int LeeMedia() const { return Media; }
   
  private:
   int Valor;
   static int Cuenta;
   static int Suma;
   static int Media;
   
   void CalculaMedia();
};
 
Numero::Numero(int v) 
{ 
   Valor = v; 
   Cuenta++; 
   Suma += Valor;
   CalculaMedia();
}
 
Numero::~Numero()
{
   Cuenta--;
   Suma -= Valor;
   CalculaMedia();
}
 
void Numero::Modifica(int v)
{
   Suma -= Valor;
   Valor = v; 
   Suma += Valor;
   CalculaMedia();
}
 
// Inicialización de miembros estáticos
int Numero::Cuenta = 0;
int Numero::Suma = 0;
int Numero::Media = 0;
 
void Numero::CalculaMedia()
{
   if(Cuenta > 0) Media = Suma/Cuenta;
   else Media = 0;
}
 
int main()
{
   Numero A(6), B(3), C(9), D(18), E(3);
   Numero *X;
 
   cout << "INICIAL" << endl;
   cout << "Cuenta: " << A.LeeCuenta() << endl;
   cout << "Media:  " << A.LeeMedia() << endl;

   B.Modifica(11);
   cout << "Modificamos B=11" << endl;
   cout << "Cuenta: " << B.LeeCuenta() << endl;
   cout << "Media:  " << B.LeeMedia() << endl;
    
   X = new Numero(548);
   cout << "Nuevo elemento dinámico de valor 548" << endl;
   cout << "Cuenta: " << X->LeeCuenta() << endl;
   cout << "Media:  " << X->LeeMedia() << endl;

   delete X;   
   cout << "Borramos el elemento dinámico" << endl;
   cout << "Cuenta: " << D.LeeCuenta() << endl;
   cout << "Media:  " << D.LeeMedia() << endl;

   system("PAUSE");
   return 0;
}

Observa que es necesario inicializar los miembros static de la clase, esto es por dos motivos. El primero es que los miembros static existen aunque no exista ningún objeto de la clase. El segundo es porque no lo hiciéramos, al declarar objetos de esa clase los valores de los miembros estáticos estarían indefinidos, y los resultados no serían los esperados.

En el caso de la funciones miembro static la utilidad es menos evidente. Estas funciones no pueden acceder a los miembros de los objetos, sólo pueden acceder a los datos miembro de la clase que sean static. Esto significa que no tienen puntero this, y además suelen ser usadas con su nombre completo, incluyendo el nombre de la clase y el operador de ámbito (::).

Por ejemplo:

#include <iostream.h>
#include <stdlib.h>
 
class Numero {
  public:
   Numero(int v = 0);
   
   void Modifica(int v) { Valor = v; }
   int LeeValor() const { return Valor; }
   int LeeDeclaraciones() const { return ObjetosDeclarados; }
   static void Reset() { ObjetosDeclarados = 0; }
   
  private:
   int Valor;
   static int ObjetosDeclarados;
};
 
Numero::Numero(int v) 
{ 
   Valor = v; 
   ObjetosDeclarados++; 
}
 
int Numero::ObjetosDeclarados = 0;
 
int main()
{
   Numero A(6), B(3), C(9), D(18), E(3);
   Numero *X;
 
   cout << "INICIAL" << endl;
   cout << "Objetos de la clase Numeros: " << A.LeeDeclaraciones() << endl;

   Numero::Reset();
   cout << "RESET" << endl;
   cout << "Objetos de la clase Numeros: " << A.LeeDeclaraciones() << endl;
   
   X = new Numero(548);
   cout << "Cuenta de objetos dinámicos declarados" << endl;
   cout << "Objetos de la clase Numeros: " << A.LeeDeclaraciones() << endl;

   delete X;   
   X = new Numero(8);
   cout << "Cuenta de objetos dinámicos declarados" << endl;
   cout << "Objetos de la clase Numeros: " << A.LeeDeclaraciones() << endl;

   delete X;
   system("PAUSE");
   return 0;
}

Observa cómo hemos llamado a la función Reset con su nombre completo. Aunque podríamos haber usado "A.Reset()", es más lógico usar el nombre completo, ya que la función puede ser invocada aunque no exista ningún objeto de la clase.


pagina033 Principal pagina034