10.3.3. Métodos estáticos

También puede crear métodos estáticos que, como los atributos estáticos, trabajan para la clase como un todo en lugar de para un objeto particular de la clase. En lugar de hacer una función global que viva en y «contamine» el espacio de nombres global o local, puede incluir el método dentro de la clase. Cuando crea un método estático, está expresando una asociación con una clase particular.

Puede llamar a un miembro estáticos de la forma habitual, con el punto o la flecha, en asociación con un objeto. De todas formas, es más típico llamar a los métodos estáticos en si mismos, sin especificar ningún objeto, utilizando el operador de resolución de ámbito, como en el siguiente ejemplo:

//: C10:SimpleStaticMemberFunction.cpp 
class X {
public:
  static void f(){};
};

int main() {
  X::f();
} ///:~

Listado 10.22. C10/SimpleStaticMemberFunction.cpp


Cuando vea métodos estáticos en una clase, recuerde que el diseñador pretendía que esa función estuviese conceptualmente asociada a la clase como un todo.

Un método estático no puede acceder a los atributos ordinarios, sólo a los estáticos. Sólo puede llamar a otros métodos estáticos. Normalmente, la dirección del objeto actual (this) se pasa de forma encubierta cuando se llama a cualquier método, pero un miembro static no tiene this, que es la razón por la cual no puede acceder a los miembros ordinarios. Por tanto, se obtiene el ligero incremento de velocidad proporcionado por una función global debido a que un método estático no implica la carga extra de tener que pasar this. Al mismo tiempo, obtiene los beneficios de tener la función dentro de la clase.

Para atributos, static indica que sólo existe un espacio de memoria por atributo para todos los objetos de la clase. Esto establece que el uso de static para definir objetos dentro de una función significa que sólo se utiliza una copia de una variable local para todas las llamadas a esa función.

Aquí aparece un ejemplo mostrando atributos y métodos estáticos utilizados conjuntamente:

//: C10:StaticMemberFunctions.cpp
class X {
  int i;
  static int j;
public:
  X(int ii = 0) : i(ii) {
     // Non-static member function can access
     // static member function or data:
    j = i;
  }
  int val() const { return i; }
  static int incr() {
    //! i++; // Error: static member function
    // cannot access non-static member data
    return ++j;
  }
  static int f() {
    //! val(); // Error: static member function
    // cannot access non-static member function
    return incr(); // OK -- calls static
  }
};

int X::j = 0;

int main() {
  X x;
  X* xp = &x;
  x.f();
  xp->f();
  X::f(); // Only works with static members
} ///:~

Listado 10.23. C10/StaticMemberFunctions.cpp


Puesto que no tienen el puntero this, los métodos estáticos no pueden acceder a atributos no estáticos ni llamar a métodos no estáticos.

Note el lector que en main() un miembro estático puede seleccionarse utilizando la habitual sintaxis de punto o flecha, asociando la función con el objeto, pero también sin objeto (ya que un miembro estático está asociado con una clase, no con un objeto particular), utilizando el nombre de la clase y el operador de resolución de ámbito.

He aquí una interesante característica: Debido a la forma en la que se inicializan los objetos miembro estáticos, es posible poner un atributos estático de la misma clase dento de dicha clase. He aquí un ejemplo que tan solo permite la existencia de un único objeto de tipo Egg definiendo el constructor privado. Puede acceder a este objeto pero no puede crear ningún otro objeto tipo Egg:

//: C10:Singleton.cpp
// Static member of same type, ensures that
// only one object of this type exists.
// Also referred to as the "singleton" pattern.
#include <iostream>
using namespace std;

class Egg {
  static Egg e;
  int i;
  Egg(int ii) : i(ii) {}
  Egg(const Egg&); // Prevent copy-construction
public:
  static Egg* instance() { return &e; }
  int val() const { return i; }
};

Egg Egg::e(47);

int main() {
//!  Egg x(1); // Error -- can't create an Egg
  // You can access the single instance:
  cout << Egg::instance()->val() << endl;
} ///:~

Listado 10.24. C10/Singleton.cpp


La inicialización de e ocurre una vez se completa la declaración de la clase, por lo que el compilador tiene toda la información que necesita para reservar espacio y llamar al constructor.

Para impedir completamente la creación de cualquier otro objeto, se ha añadido algo más: un segundo constructor privado llamado constructor de copia. Llegados a este punto del libro, usted no puede saber porqué esto es necesario puesto que el constructor de copia no se estudiará hasta el siguiente capítulo. De todas formas, como un breve adelanto, si se propusiese retirar el constructor de copia definido en el ejemplo anterior, sería posible crear objetos Egg de la siguiente forma:

Egg e = *Egg::instance();
Egg e2(*Egg::instance());

Ambos utilizan el constructor de copia, por lo que para evitar esta posibilidad, se declara el constructor de copia como privado (no se requiere definición porque nunca va a ser llamado). Buena parte del siguiente capítulo es una discusión sobre el constructor de copia por lo que esto quedará más claro entonces.