Qual é o idioma do C ++ equivalente ao bloco estático de Java?

Eu tenho uma class com alguns membros estáticos, e eu quero executar algum código para inicializá-los (suponha que este código não pode ser convertido em uma expressão simples). Em Java, eu apenas faria

class MyClass { static int myDatum; static { /* do some computation which sets myDatum */ } } 

A menos que eu esteja enganado, o C ++ não permite esses blocos de código estáticos, certo? O que eu deveria estar fazendo em vez disso?

Eu gostaria de solução para as duas opções a seguir:

  1. A boot acontece quando o processo é carregado (ou quando a DLL com essa class é carregada).
  2. Inicialização acontece quando a class é primeiro instanciada.

Para a segunda opção, eu estava pensando em:

 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 */ } }; 

mas isso não é possível, já que o C ++ (no momento?) não permite a boot de membros estáticos não-constantes. Mas, pelo menos isso reduz o problema de um bloco estático para o de boot estática por expressão …

Para o número 1, se você realmente precisar inicializar quando o processo for iniciado / a biblioteca for carregada, será necessário usar algo específico da plataforma (como o DllMain no Windows).

No entanto, se for suficiente executar a boot antes de qualquer código do mesmo arquivo .cpp que a estática for executada, o seguinte deverá funcionar:

 // Header: class MyClass { static int myDatum; static int initDatum(); }; 

 // .cpp file: int MyClass::myDatum = MyClass::initDatum(); 

Desta forma, initDatum() é garantido para ser chamado antes que qualquer código desse arquivo .cpp seja executado.

Se você não quiser poluir a definição de class, você também pode usar um Lambda (C ++ 11):

 // Header: class MyClass { static int myDatum; }; 

 // .cpp file: int MyClass::myDatum = []() -> int { /*any code here*/ return /*something*/; }(); 

Não esqueça o último par de parênteses – que na verdade chama o lambda.


Quanto ao número 2, há um problema: você não pode chamar uma function virtual no construtor. É melhor fazer isso manualmente na class em vez de usar uma class base para isso:

 class MyClass { static int myDatum; MyClass() { static bool onlyOnce = []() -> bool { MyClass::myDatum = /*whatever*/; return true; } } }; 

Supondo que a class tenha apenas um construtor, isso funcionará bem; ele é thread-safe, pois o C ++ 11 garante essa segurança para inicializar variables ​​locais estáticas.

Você pode ter blocos estáticos em C ++ também – classs externas.

Acontece que podemos implementar um bloco estático no estilo Java, embora fora de uma class em vez de dentro dela, ou seja, no escopo da unidade de tradução. A implementação é um pouco feia, mas quando usada é bem elegante!

Uso

Se você escrever:

 static_block { std::cout << "Hello static block world!" << std::endl; } 

este código será executado antes do seu main() . E você pode inicializar variables ​​estáticas ou fazer o que quiser. Então você pode colocar tal bloco no arquivo de implementação .cpp sua class.

Notas:

  • Você deve cercar seu código de bloco estático com chaves.
  • A ordem relativa de execução de código estático não é garantida em C ++ .

Implementação

A implementação do bloco estático envolve uma variável fictícia inicializada estaticamente com uma function. Seu bloco estático é, na verdade, o corpo dessa function. Para garantir que não colidamos com alguma outra variável fictícia (por exemplo, de outro bloco estático - ou em qualquer outro lugar), precisamos de um pouco de maquinário 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 */ #else #endif /* UNIQUE_IDENTIFIER */ 

e aqui está o trabalho macro para juntar as coisas:

 #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:

  • Alguns compiladores não suportam __COUNTER__ - não faz parte do padrão C ++; Nesses casos, o código acima usa __LINE__ , que também funciona. O GCC e o Clang suportam __COUNTER__ .
  • Este é o C ++ 98; você não precisa de nenhuma construção C ++ 11/14/17. No entanto, não é válido C, apesar de não usar nenhuma class ou método.
  • O __attribute ((unused)) pode ser removido ou substituído por [[unused]] se você tiver um compilador C ++ 11 que não goste da extensão não utilizada do estilo GCC.
  • Isto não evita ou ajuda com o fiasco da ordem de boot estática , já que enquanto você sabe que seu bloco estático será executado antes de main() , você não está garantido quando exatamente isso acontece em relação a outras inicializações estáticas.

Eu estou usando isso no meu trabalho de programação real, então você pode pegá-lo como um arquivo .h daqui .

Você pode inicializar membros de dados estáticos em C ++:

 #include "Bar.h" Bar make_a_bar(); struct Foo { static Bar bar; }; Bar Foo::bar = make_a_bar(); 

Talvez seja necessário pensar nas dependencies entre unidades de tradução, mas essa é a abordagem geral.

Aqui está uma boa maneira de imitar um bloco static usando o 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 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"; }; } 

Demo

Você pode ser melhor tomar uma abordagem completamente diferente. A coleta de informações estáticas realmente precisa ser definida dentro de StaticInitialized?

Considere criar uma class singleton separada chamada SharedData. O primeiro cliente que chama SharedData :: Instance (), em seguida, irá acionar a criação da coleção de dados compartilhados, que será simplesmente dados de class normal, embora vivendo dentro de uma instância de object único que é alocada estaticamente:

// 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; } 

Qualquer cliente interessado na coleta compartilhada de dados teria que acessá-lo via singleton SharedData, e o primeiro cliente a chamar SharedData :: instance () acionaria a configuração desses dados, no cador SharedData, que só seria chamado uma vez.

Agora, seu código sugere que diferentes subclasss podem ter suas próprias formas de inicializar dados estáticos (por meio da natureza polimórfica de initializeStatics ()). Mas isso parece uma ideia bastante problemática. Várias classs derivadas realmente pretendem compartilhar um único dataset estáticos, mas cada subclass o inicializaria de forma diferente? Isso significaria simplesmente que qualquer class que fosse construída primeiro seria a única a configurar os dados estáticos em sua própria maneira paroquial, e então todas as outras classs teriam que usar essa configuração. É mesmo isto que queres?

Também estou um pouco confuso sobre por que você tentaria combinar polymorphism com inheritance privada. O número de casos em que você deseja genuinamente usar a inheritance privada (em oposição à composição) é muito pequeno. Estou me perguntando se você de alguma forma acredita que você precisa de initializeStatics () para ser virtual para que a class derivada possa chamá-lo. (Este não é o caso.) No entanto, você parece querer sobrescrever initializeStatics () na class derivada, por razões que não são claras para mim (veja anteriormente). Algo parece estranho em toda a configuração.

Em C ++ não existe tal idioma.

O motivo está na natureza inteiramente diferente do código gerado a partir do C ++: O tempo de execução não é “gerenciado”. No código gerado, após a compilation, não existe mais a noção de “class”, e não existem entidades de código carregadas sob demanda por um “classloader”.

Existem alguns elementos com comportamento mais ou menos comparável, mas você realmente precisa entender sua natureza precisamente para explorar esse comportamento.

  • você pode construir seu código em uma biblioteca compartilhada, que pode ser carregada dinamicamente, em tempo de execução.
  • em C ++ 11, você pode std :: call_once sua boot de um construtor de class. No entanto, esse código será executado com atraso, quando a instância da class for criada, e não quando a biblioteca executável ou compartilhada for carregada
  • você pode definir variables ​​globais e variables ​​estáticas (de class) com um inicializador. Este inicializador pode ser uma function, que permite executar código quando a variável é inicializada. A ordem de execução desses inicializadores é bem definida apenas dentro de uma única unidade de tradução (por exemplo, um arquivo *.cpp ).

Mas você não deve assumir nada além disso; esp. você nunca pode ter certeza se e quando essa boot é realmente executada. Este aviso é real . Especialmente , não assuma nada sobre os efeitos colaterais desse código de boot. É perfeitamente legal para o compilador replace esse código por algo considerado “equivalente” pelo compilador. Futuras versões de compiladores podem ser consideradas mais inteligentes nesse aspecto. Seu código pode parecer funcionar, mas pode quebrar com diferentes sinalizadores de otimização, processo de compilation diferente, versão mais recente do compilador.


Dica prática : se você se encontrar na situação em que você tem várias variables ​​estáticas, as quais você precisa inicializar corretamente, então é provável que você queira classificá-las em uma class. Esta class pode então ter um construtor e um destruidor regulares para fazer a boot / limpeza. Você pode então colocar uma instância dessa class auxiliar em uma única variável estática (class). O C ++ fornece garantias de consistência muito fortes para invocação de ctors e dtors de classs, para qualquer coisa que seja acessível por meios oficiais (sem lançamentos, sem truques de baixo nível).