¿Cómo se crea una clase estática en C ++?

264

¿Cómo se crea una clase estática en C ++? Debería poder hacer algo como:

cout << "bit 5 is " << BitParser::getBitAt(buffer, 5) << endl;

Asumiendo que creé la BitParserclase. ¿Cómo sería la BitParserdefinición de clase?

andrewrk
fuente
198
Sí. En los viejos tiempos, simplemente lo llamamos una "función". Ustedes niños hoy con sus "espacios de nombres" locos. ¡Oye, sal de mi césped! <sacude el puño>
Vagabundo
77
@Vagrant una función dentro de un espacio de nombres sigue siendo una función. Una función que pertenece a una clase se llama método. Si es un método estático, lo invoca de manera similar como si fuera una función dentro de un espacio de nombres.
48
5 años después y ahora estoy del lado de @Vagrant. ¡Qué pregunta tonta!
Andrew
16
9 años después y ahora tengo mi propio lenguaje de programación que ni siquiera tiene clases: ziglang.org
andrewrk
1
Las clases tipo contenedor IMO (que tienen solo métodos estáticos) son útiles en ciertos casos.
AarCee

Respuestas:

272

Si está buscando una manera de aplicar la palabra clave "estática" a una clase, como puede hacerlo en C #, por ejemplo, no podrá hacerlo sin usar C ++ administrado.

Pero el aspecto de su muestra, solo necesita crear un método público estático en su objeto BitParser. Al igual que:

BitParser.h

class BitParser
{
 public:
  static bool getBitAt(int buffer, int bitIndex);

  // ...lots of great stuff

 private:
  // Disallow creating an instance of this object
  BitParser() {}
};

BitParser.cpp

bool BitParser::getBitAt(int buffer, int bitIndex)
{
  bool isBitSet = false;
  // .. determine if bit is set
  return isBitSet;
}

Puede usar este código para llamar al método de la misma manera que su código de ejemplo.

¡Espero que ayude! Salud.

DO
fuente
2
OJ, tiene un error de sintaxis . La palabra clave estática solo debe usarse en la definición de clase, y no en la definición del método.
andrewrk
90
Para aclarar su intención en este enfoque, también puede utilizar un constructor privado. private: BitParser() {}Esto evitará que cualquiera cree instancias.
Danvil
77
La seguridad de subprocesos de @MoatazElmasry es un problema cuando compartes estado. En la implementación anterior no hay un estado compartido, por lo que no puede haber ningún problema con la seguridad de subprocesos ... a menos que seas lo suficientemente estúpido como para usar estadísticas dentro de esas funciones. Entonces, sí, el código anterior es seguro para subprocesos, solo mantenga el estado persistente fuera de sus funciones y estará bien.
DO
@MoatazElmasry Incorrecto. Dos hilos no pueden modificar variables locales no estáticas en una función estática.
DO
12
Si es C ++ 11, diría que es mejor BitParser() = delete;transmitir adecuadamente la intención de eliminar el constructor (no solo ocultarlo como private).
Phoenix
247

Considere la solución de Matt Price .

  1. En C ++, una "clase estática" no tiene significado. Lo más cercano es una clase con solo métodos estáticos y miembros.
  2. Usar métodos estáticos solo te limitará.

Lo que desea es, expresado en semántica en C ++, colocar su función (porque es una función) en un espacio de nombres.

Editar 2011-11-11

No hay "clase estática" en C ++. El concepto más cercano sería una clase con solo métodos estáticos. Por ejemplo:

// header
class MyClass
{
   public :
      static void myMethod() ;
} ;

// source
void MyClass::myMethod()
{
   // etc.
}

Pero debe recordar que las "clases estáticas" son hacks en el tipo de lenguajes similares a Java (por ejemplo, C #) que no pueden tener funciones que no sean miembros, por lo que tienen que moverlas dentro de las clases como métodos estáticos.

En C ++, lo que realmente quiere es una función no miembro que declarará en un espacio de nombres:

// header
namespace MyNamespace
{
   void myMethod() ;
}

// source
namespace MyNamespace
{
   void myMethod()
   {
      // etc.
   }
}

¿Porqué es eso?

En C ++, el espacio de nombres es más poderoso que las clases para el patrón "Método estático Java", porque:

  • los métodos estáticos tienen acceso a las clases símbolos privados
  • Los métodos estáticos privados siguen siendo visibles (si no son accesibles) para todos, lo que infringe algo la encapsulación
  • los métodos estáticos no se pueden declarar hacia adelante
  • El usuario de la clase no puede sobrecargar los métodos estáticos sin modificar el encabezado de la biblioteca
  • no hay nada que se pueda hacer con un método estático que no se pueda hacer mejor que una función no miembro (posiblemente amiga) en el mismo espacio de nombres
  • los espacios de nombres tienen su propia semántica (se pueden combinar, pueden ser anónimos, etc.)
  • etc.

Conclusión: No copie / pegue el patrón de Java / C # en C ++. En Java / C #, el patrón es obligatorio. Pero en C ++, es un mal estilo.

Editar 2010-06-10

Hubo un argumento a favor del método estático porque a veces, uno necesita usar una variable miembro privada estática.

No estoy de acuerdo, como se muestra a continuación:

La solución "Miembro privado estático"

// HPP

class Foo
{
   public :
      void barA() ;
   private :
      void barB() ;
      static std::string myGlobal ;
} ;

Primero, myGlobal se llama myGlobal porque sigue siendo una variable privada global. Un vistazo a la fuente de CPP aclarará que:

// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP

void Foo::barA()
{
   // I can access Foo::myGlobal
}

void Foo::barB()
{
   // I can access Foo::myGlobal, too
}

void barC()
{
   // I CAN'T access Foo::myGlobal !!!
}

A primera vista, el hecho de que la función gratuita barC no pueda acceder a Foo :: myGlobal parece algo bueno desde el punto de vista de la encapsulación ... Es genial porque alguien que mira el HPP no podrá (a menos que recurra al sabotaje) acceder Foo :: myGlobal.

Pero si lo observa detenidamente, encontrará que es un error colosal: no solo su variable privada debe ser declarada en el HPP (y, por lo tanto, visible para todo el mundo, a pesar de ser privada), sino que debe declarar en la misma HPP todas (como en TODAS) funciones que estarán autorizadas para acceder a ella !!!

Por lo tanto, usar un miembro estático privado es como caminar desnudo afuera con la lista de tus amantes tatuada en tu piel: nadie está autorizado a tocar, pero todos pueden mirar. Y la bonificación: todos pueden tener los nombres de aquellos autorizados para jugar con sus privilegios.

private de hecho ... :-D

La solución "Espacios de nombres anónimos"

Los espacios de nombres anónimos tendrán la ventaja de hacer que las cosas privadas sean realmente privadas.

Primero, el encabezado HPP

// HPP

namespace Foo
{
   void barA() ;
}

Solo para asegurarme de haber comentado: no hay una declaración inútil de barB ni myGlobal. Lo que significa que nadie que lea el encabezado sabe lo que está oculto detrás de la barra A.

Entonces, el CPP:

// CPP
namespace Foo
{
   namespace
   {
      std::string myGlobal ;

      void Foo::barB()
      {
         // I can access Foo::myGlobal
      }
   }

   void barA()
   {
      // I can access myGlobal, too
   }
}

void barC()
{
   // I STILL CAN'T access myGlobal !!!
}

Como puede ver, al igual que la llamada declaración de "clase estática", fooA y fooB aún pueden acceder a myGlobal. Pero nadie más puede. ¡Y nadie más fuera de este CPP sabe que existen fooB y myGlobal!

A diferencia de la "clase estática" que camina desnudo con su libreta de direcciones tatuada en su piel, el espacio de nombres "anónimo" está completamente vestido , lo que parece bastante mejor encapsulado AFAIK.

¿Realmente importa?

A menos que los usuarios de su código sean saboteadores (les dejaré, como ejercicio, encontrar cómo se puede acceder a la parte privada de una clase pública usando un truco sucio de comportamiento indefinido ...), lo que privatees private, incluso si es visible en la privatesección de una clase declarada en un encabezado.

Aún así, si necesita agregar otra "función privada" con acceso al miembro privado, debe declararlo a todo el mundo modificando el encabezado, lo que es una paradoja en lo que a mí respecta: si cambio la implementación de mi código (la parte CPP), entonces la interfaz (la parte HPP) NO debería cambiar. Citando a Leonidas: "¡ Esto es ENCAPSULACIÓN! "

Editar 2014-09-20

¿Cuándo son realmente mejores los métodos estáticos de clases que los espacios de nombres con funciones que no son miembros?

Cuando necesite agrupar funciones y alimentar ese grupo a una plantilla:

namespace alpha
{
   void foo() ;
   void bar() ;
}

struct Beta
{
   static void foo() ;
   static void bar() ;
};

template <typename T>
struct Gamma
{
   void foobar()
   {
      T::foo() ;
      T::bar() ;
   }
};

Gamma<alpha> ga ; // compilation error
Gamma<Beta> gb ;  // ok
gb.foobar() ;     // ok !!!

Porque, si una clase puede ser un parámetro de plantilla, un espacio de nombres no puede.

paercebal
fuente
3
GCC admite -fno-access-control, que se puede usar en pruebas de unidad de caja blanca para acceder a miembros de clase que de otro modo serían privados. Esa es la única razón por la que puedo pensar para justificar el uso de un miembro de la clase en lugar de un global anónimo / estático en la implementación.
Tom
8
@Tom: Una solución multiplataforma sería agregar el siguiente código #define private publicen los encabezados ... ^ _ ^ ...
paercebal
1
@Tom: de todos modos, en mi humilde opinión, incluso teniendo en cuenta las pruebas unitarias, las desventajas de "mostrar demasiadas cosas" supera a los profesionales. Supongo que una solución alternativa sería poner el código que se probará en una función que tome los parámetros necesarios (y no más) en un utilitiesespacio de nombres. De esta manera, esta función puede ser la unidad probada, y aún no tienen acceso especial a los miembros privados (ya que se dan como parámetros en la llamada de función) ...
paercebal
@paercebal Estoy a punto de saltar a bordo de su barco, pero tengo una reserva final. Si alguien salta en tu namespacevoluntad, ¿no tendrán acceso a tus globalmiembros, aunque estén ocultos? Obviamente tendrían que adivinar, pero a menos que esté ofuscando intencionalmente su código, los nombres de las variables son bastante fáciles de adivinar.
Zak
@Zak: De hecho, podrían hacerlo, pero solo tratando de hacerlo en el archivo CPP donde se declara la variable myGlobal. El punto es más visibilidad que accesibilidad. En la clase estática, la variable myGlobal es privada, pero aún visible. Esto no es tan importante como parece, pero aún así, en una DLL, mostrar un símbolo que debería ser privado para la DLL en un encabezado exportado podría ser incómodo ... En el espacio de nombres, myGlobal solo existe en el archivo CPP (usted incluso puede ir más lejos y hacerlo estático). Esa variable no aparece en los encabezados públicos.
paercebal
63

También puede crear una función libre en un espacio de nombres:

En BitParser.h

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex);
}

En BitParser.cpp

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex)
    {
        //get the bit :)
    }
}

En general, esta sería la forma preferida de escribir el código. Cuando no hay necesidad de un objeto, no use una clase.

Matt Price
fuente
1
En algunos casos, es posible que desee tener la encapsulación de datos incluso si la clase es principalmente "estática". Los miembros de la clase privada estática te darán esto. Los miembros del espacio de nombres son siempre públicos y no pueden proporcionar la encapsulación de datos.
Torleif
Si la var "miembro" solo se declara y se accede desde el archivo .cpp, es más privada que una var privada declarada en el archivo .h. NO que recomiendo esta técnica.
jmucchiello
3
@Torleif: Estás equivocado. los espacios de nombres son mejores para la encapsulación que los miembros privados estáticos. Vea mi respuesta para una demostración.
paercebal
1
sí, pero en el espacio de nombres debe mantener el orden de las funciones, en contraste con la clase con miembros estáticos, por ejemplo, vacío a () {b ();} b () {} produciría un error en un espacio de nombres pero no en una clase con miembros estáticos
Moataz Elmasry
13

Si está buscando una manera de aplicar la palabra clave "estática" a una clase, como puede hacer en C #, por ejemplo

Las clases estáticas son solo el compilador que te sujeta a la mano y te impide escribir cualquier método / variable de instancia.

Si solo escribe una clase normal sin ningún método / variable de instancia, es lo mismo, y esto es lo que haría en C ++

Orion Edwards
fuente
No me quejo (especialmente a usted), pero algo de compilación de mano para evitar que escriba o corte / pegue la palabra static200 veces sería algo bueno.
3Dave
De acuerdo, pero una clase estática en C # tampoco hace esto. Simplemente no se compila cuando olvida pegar estática allí :-)
Orion Edwards
Sí, lo suficientemente justo. Mis macros se muestran. Honestamente, si declaro que la clase es estática, el compilador solo debería arrojar un error si intento crear una instancia. Las reglas que requieren que me repita son desagradables y deberían ser las primeras contra la pared cuando llegue la revolución.
3Dave
11

En C ++ desea crear una función estática de una clase (no una clase estática).

class BitParser {
public:
  ...
  static ... getBitAt(...) {
  }
};

Entonces debería poder llamar a la función usando BitParser :: getBitAt () sin crear una instancia de un objeto que supongo que es el resultado deseado.

Philip Reynolds
fuente
11

¿Puedo escribir algo como static class?

No , de acuerdo con el borrador estándar C ++ 11 N3337 del Anexo C 7.1.1:

Cambio: en C ++, los especificadores estáticos o externos solo pueden aplicarse a nombres de objetos o funciones. El uso de estos especificadores con declaraciones de tipo es ilegal en C ++. En C, estos especificadores se ignoran cuando se usan en declaraciones de tipo. Ejemplo:

static struct S {    // valid C, invalid in C++
  int i;
};

Justificación: los especificadores de clase de almacenamiento no tienen ningún significado cuando se asocian con un tipo. En C ++, los miembros de la clase se pueden declarar con el especificador de clase de almacenamiento estático. Permitir que los especificadores de clase de almacenamiento en las declaraciones de tipo pueda hacer que el código sea confuso para los usuarios.

Y al igual que struct,class también es una declaración de tipo.

Lo mismo puede deducirse caminando por el árbol de sintaxis en el Anexo A.

Es interesante notar que static structera legal en C, pero no tuvo efecto: ¿por qué y cuándo usar estructuras estáticas en la programación en C?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
6

Como se ha señalado aquí, una mejor manera de lograr esto en C ++ podría ser usar espacios de nombres. Pero como nadie ha mencionado la finalpalabra clave aquí, estoy publicando lo que sería un equivalente directo de static classC # en C ++ 11 o posterior:

class BitParser final
{
public:
  BitParser() = delete;

  static bool GetBitAt(int buffer, int pos);
};

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}
Mikhail Vasilyev
fuente
5

Puede 'tener' una clase estática en C ++, como se mencionó anteriormente, una clase estática es aquella que no tiene ningún objeto de ella instanciada. En C ++, esto se puede obtener declarando el constructor / destructor como privado. El resultado final es el mismo.

Netzer
fuente
Lo que está sugiriendo podría crear una clase singleton, pero no es lo mismo que una clase estática.
ksinkar
4

En Managed C ++, la sintaxis de clase estática es: -

public ref class BitParser abstract sealed
{
    public:
        static bool GetBitAt(...)
        {
            ...
        }
}

... mejor tarde que nunca...

Malc B
fuente
3

Esto es similar a la forma de C # de hacerlo en C ++

En C # file.cs puede tener var privada dentro de una función pública. Cuando esté en otro archivo, puede usarlo llamando al espacio de nombres con la función como en:

MyNamespace.Function(blah);

Aquí se explica cómo hacer lo mismo en C ++:

SharedModule.h

class TheDataToBeHidden
{
  public:
    static int _var1;
    static int _var2;
};

namespace SharedData
{
  void SetError(const char *Message, const char *Title);
  void DisplayError(void);
}

SharedModule.cpp

//Init the data (Link error if not done)
int TheDataToBeHidden::_var1 = 0;
int TheDataToBeHidden::_var2 = 0;


//Implement the namespace
namespace SharedData
{
  void SetError(const char *Message, const char *Title)
  {
    //blah using TheDataToBeHidden::_var1, etc
  }

  void DisplayError(void)
  {
    //blah
  }
}

OtherFile.h

#include "SharedModule.h"

OtherFile.cpp

//Call the functions using the hidden variables
SharedData::SetError("Hello", "World");
SharedData::DisplayError();
Juan
fuente
2
Pero todos pueden ir a TheDataToBeHidden -> No es una solución
Guy L
3

A diferencia de otro lenguaje de programación administrado, "clase estática" NO tiene significado en C ++. Puede hacer uso de la función miembro estática.

Bharath Ravindra
fuente
0

Un caso en el que los espacios de nombres pueden no ser tan útiles para lograr "clases estáticas" es cuando se usan estas clases para lograr la composición sobre la herencia. Los espacios de nombres no pueden ser amigos de clases y, por lo tanto, no pueden acceder a miembros privados de una clase.

class Class {
 public:
  void foo() { Static::bar(*this); }    

 private:
  int member{0};
  friend class Static;
};    

class Static {
 public:
  template <typename T>
  static void bar(T& t) {
    t.member = 1;
  }
};
Josh Olson
fuente
0

Una (de las muchas) alternativas, pero la más elegante (en mi opinión) elegante (en comparación con el uso de espacios de nombres y constructores privados para emular el comportamiento estático), la forma de lograr el comportamiento de "clase que no se puede instanciar" en C ++ sería declarar una función virtual pura ficticia con el privatemodificador de acceso.

class Foo {
   public:
     static int someMethod(int someArg);

   private:
     virtual void __dummy() = 0;
};

Si está utilizando C ++ 11, podría hacer un esfuerzo adicional para asegurarse de que la clase no se herede (para emular puramente el comportamiento de una clase estática) utilizando el finalespecificador en la declaración de clase para restringir la herencia de las otras clases. .

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
      virtual void __dummy() = 0;
};

Por tonto e ilógico que pueda parecer, C ++ 11 permite la declaración de una "función virtual pura que no se puede anular", que puede usar junto con la declaración de la clase finalpara implementar pura y completamente el comportamiento estático, ya que esto da como resultado el resultado que la clase no sea heredable y que la función ficticia no se anule de ninguna manera.

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
     // Other private declarations

     virtual void __dummy() = 0 final;
}; // Foo now exhibits all the properties of a static class
Hécate
fuente