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.