usar - static final java
¿Cuál es el lenguaje C++ equivalente al bloque estático de Java? (6)
En C ++ no hay tal idioma.
La razón radica en la naturaleza completamente diferente del código generado desde C ++: el tiempo de ejecución no está "administrado". En el código generado, después de la compilación, ya no existe la noción de una "clase", y no existe tal cosa como las entidades de código cargadas a pedido por un "cargador de clases".
Hay algunos elementos con un comportamiento más o menos comparable, pero realmente necesitas entender su naturaleza precisamente para explotar este comportamiento.
- puede construir su código en una biblioteca compartida, que se puede cargar dinámicamente, en tiempo de ejecución.
- en C ++ 11, puede std::call_once su inicialización desde un constructor de clase. Sin embargo, dicho código se ejecutará tarde, cuando se cree la instancia de la clase, no cuando se cargue la biblioteca ejecutable o compartida
- puede definir variables globales y (clase) variables estáticas con un inicializador. Este inicializador puede ser una función, que le permite ejecutar código cuando la variable se inicializa. El orden de ejecución de estos inicializadores está bien definido solo dentro de una sola unidad de traducción (por ejemplo, un archivo
*.cpp
).
Pero no debes asumir nada más allá de eso; especialmente nunca se puede estar seguro si y cuando esta inicialización se realiza realmente. Esta advertencia es de verdad . Especialmente , no asuma nada sobre los efectos secundarios de dicho código de inicialización. Es perfectamente legal que el compilador reemplace dicho código por algo que el compilador considere "equivalente". Se puede asumir que las futuras versiones de compiladores se vuelven cada vez más inteligentes a este respecto. Su código puede parecer que funciona, pero puede romperse con diferentes indicadores de optimización, diferentes procesos de compilación y nuevas versiones del compilador.
Consejo práctico : si se encuentra en la situación de que tiene varias variables estáticas, que necesita inicializar correctamente, entonces es probable que desee incluirlas en una clase. Esta clase puede tener un constructor y destructor regulares para realizar la inicialización / limpieza. Luego, puede colocar una instancia de esa clase auxiliar en una sola variable estática (clase). C ++ ofrece garantías de consistencia muy fuertes para invocar a los conductores y a los conductores de clases, para cualquier cosa que sea accesible por medios oficiales (sin lanzamientos, sin trucos de bajo nivel).
Tengo una clase con algunos miembros estáticos y quiero ejecutar un código para inicializarlos (suponga que este código no se puede convertir en una expresión simple). En Java, yo solo haría
class MyClass {
static int myDatum;
static {
/* do some computation which sets myDatum */
}
}
A menos que me equivoque, C ++ no permite tales bloques de código estático, ¿verdad? ¿Qué debería estar haciendo en su lugar?
Me gustaría una solución para las dos opciones siguientes:
- La inicialización ocurre cuando se carga el proceso (o cuando se carga la DLL con esta clase).
- La inicialización ocurre cuando la clase es instanciada por primera vez.
Para la segunda opción, estaba pensando en:
class StaticInitialized {
static bool staticsInitialized = false;
virtual void initializeStatics();
StaticInitialized() {
if (!staticsInitialized) {
initializeStatics();
staticsInitialized = true;
}
}
};
class MyClass : private StaticInitialized {
static int myDatum;
void initializeStatics() {
/* computation which sets myDatum */
}
};
pero eso no es posible, ya que C ++ (¿en este momento?) no permite la inicialización de miembros estáticos no constantes. Pero, al menos eso reduce el problema de un bloque estático al de la inicialización estática por expresión ...
También puede tener bloques estáticos en C ++ - clases externas.
Resulta que podemos implementar un bloque estático de estilo Java, aunque fuera de una clase en lugar de dentro de él, es decir, en el ámbito de la unidad de traducción. La implementación es un poco fea debajo del capó, pero cuando se usa es bastante elegante.
Uso
Si tú escribes:
static_block {
std::cout << "Hello static block world!/n";
}
este código se ejecutará antes de su main()
. Y puedes inicializar variables estáticas o hacer lo que quieras. Por lo tanto, puede colocar dicho bloque en el archivo de implementación .cpp
su clase.
Notas:
- Debe rodear su código de bloque estático con llaves.
- El orden relativo de ejecución del código estático no está garantizado en C ++ .
Implementación
La implementación del bloque estático implica una variable ficticia inicializada estáticamente con una función. Tu bloque estático es en realidad el cuerpo de esa función. Para asegurarnos de no chocar con alguna otra variable ficticia (por ejemplo, de otro bloque estático, o en cualquier otro lugar), necesitamos un poco de maquinaria macro.
#define CONCATENATE(s1, s2) s1##s2
#define EXPAND_THEN_CONCATENATE(s1, s2) CONCATENATE(s1, s2)
#ifdef __COUNTER__
#define UNIQUE_IDENTIFIER(prefix) EXPAND_THEN_CONCATENATE(prefix, __COUNTER__)
#else
#define UNIQUE_IDENTIFIER(prefix) EXPAND_THEN_CONCATENATE(prefix, __LINE__)
#endif // __COUNTER__
Y aquí está el trabajo macro para juntar las cosas:
#define static_block STATIC_BLOCK_IMPL1(UNIQUE_IDENTIFIER(_static_block_))
#define STATIC_BLOCK_IMPL1(prefix) /
STATIC_BLOCK_IMPL2(CONCATENATE(prefix,_fn),CONCATENATE(prefix,_var))
#define STATIC_BLOCK_IMPL2(function_name,var_name) /
static void function_name(); /
static int var_name __attribute((unused)) = (function_name(), 0) ; /
static void function_name()
Notas:
- Algunos compiladores no admiten
__COUNTER__
- no es parte del estándar C ++; en esos casos, el código anterior utiliza__LINE__
, que también funciona. GCC y Clang soportan__COUNTER__
. - Esto es C ++ 98; no necesitas ninguna construcción de C ++ 11/14/17. Sin embargo, no es válido C, a pesar de no usar ninguna clase o método.
- El
__attribute ((unused))
puede eliminarse o reemplazarse con[[unused]]
si tiene un compilador C ++ 11 al que no le guste la extensión no utilizada de estilo GCC. - Esto no evita ni ayuda con el fiasco de la orden de inicialización estática , ya que si bien sabe que su bloqueo estático se ejecutará antes de
main()
, no se garantiza cuándo sucederá exactamente eso con respecto a otras inicializaciones estáticas.
Esta es una buena manera de imitar un bloque static
usando C ++ 11:
Macro
#define CONCATE_(X,Y) X##Y
#define CONCATE(X,Y) CONCATE_(X,Y)
#define UNIQUE(NAME) CONCATE(NAME, __LINE__)
struct Static_
{
template<typename T> Static_ (T only_once) { only_once(); }
~Static_ () {} // to counter "warning: unused variable"
};
// `UNIQUE` macro required if we expect multiple `static` blocks in function
#define STATIC static Static_ UNIQUE(block) = [&]() -> void
Uso
void foo ()
{
std::cout << "foo()/n";
STATIC
{
std::cout << "Executes only once/n";
};
}
Para el # 1, si realmente necesita inicializar cuando se inicia el proceso / se carga la biblioteca, tendrá que usar algo específico de la plataforma (como DllMain en Windows).
Sin embargo, si es suficiente para que ejecute la inicialización antes de que se ejecute cualquier código del mismo archivo .cpp que las estadísticas, lo siguiente debería funcionar:
// Header:
class MyClass
{
static int myDatum;
static int initDatum();
};
// .cpp file:
int MyClass::myDatum = MyClass::initDatum();
De esta manera, se garantiza que se initDatum()
antes de que se .cpp
cualquier código de ese archivo .cpp
.
Si no desea contaminar la definición de clase, también puede usar un Lambda (C ++ 11):
// Header:
class MyClass
{
static int myDatum;
};
// .cpp file:
int MyClass::myDatum = []() -> int { /*any code here*/ return /*something*/; }();
No olvide el último par de paréntesis, que en realidad llama a la lambda.
En cuanto al # 2, hay un problema: no se puede llamar a una función virtual en el constructor. Es mejor hacer esto manualmente en la clase en lugar de usar una clase base para ello:
class MyClass
{
static int myDatum;
MyClass() {
static bool onlyOnce = []() -> bool {
MyClass::myDatum = /*whatever*/;
return true;
}
}
};
Asumiendo que la clase solo tiene un constructor, que funcionará bien; es seguro para subprocesos, ya que C ++ 11 garantiza dicha seguridad al inicializar variables locales estáticas.
Puede inicializar miembros de datos estáticos en C ++:
#include "Bar.h"
Bar make_a_bar();
struct Foo
{
static Bar bar;
};
Bar Foo::bar = make_a_bar();
Puede que tenga que pensar en las dependencias entre unidades de traducción, pero ese es el enfoque general.
Puede que sea mejor tomar un enfoque diferente por completo. ¿La recopilación de información estática realmente debe definirse dentro de StaticInitialized?
Considera crear una clase de singleton separada llamada SharedData. El primer cliente que llama a SharedData :: Instance () activará la creación de la recopilación de datos compartidos, que simplemente serán datos de clase normales, aunque se encuentren dentro de una única instancia de objeto que se asigna de forma estática:
// SharedData.h
class SharedData
{
public:
int m_Status;
bool m_Active;
static SharedData& instance();
private:
SharedData();
}
// SharedData.cpp
SharedData::SharedData()
: m_Status( 0 ), m_Active( true )
{}
// static
SharedData& SharedData::instance()
{
static SharedData s_Instance;
return s_Instance;
}
Cualquier cliente interesado en la recopilación compartida de datos ahora tendría que acceder a él a través de SharedData singleton, y el primer cliente que llame a SharedData :: instance () activará la configuración de esos datos, en el ctor SharedData, que solo sería Llamé una vez.
Ahora su código sugiere que diferentes subclases podrían tener sus propias formas de inicializar datos estáticos (a través de la naturaleza polimórfica de initializeStatics ()). Pero esto parece una idea bastante problemática. ¿Se pretende que las múltiples clases derivadas compartan un solo conjunto de datos estáticos, sin embargo, cada subclase lo inicializaría de manera diferente? Esto simplemente significaría que la clase que se construyó primero sería la que configuraría los datos estáticos en su propia forma parroquial, y luego todas las demás clases tendrían que usar esta configuración. ¿Es esto realmente lo que quieres?
También estoy un poco confundido en cuanto a por qué trataría de combinar el polimorfismo con la herencia privada. El número de casos en los que realmente desearía utilizar la herencia privada (en oposición a la composición) es muy pequeño. Me pregunto si de alguna manera cree que necesita que initializeStatics () sea virtual para que la clase derivada pueda llamarlo. (Este no es el caso). Sin embargo, parece que usted desea anular initializeStatics () en la clase derivada, por razones que no están claras para mí (ver más arriba). Algo parece extraño en toda la configuración.