Inicialização de membros estáticos C ++ (modelo divertido dentro)

Para boot de membro estático eu uso uma estrutura auxiliar aninhada, que funciona bem para classs sem modelo. No entanto, se a class envolvente for parametrizada por um modelo, a class de boot aninhada não será instanciada, se o object auxiliar não for acessado no código principal. Para ilustração, um exemplo simplificado (no meu caso, eu preciso inicializar um vetor).

#include  #include  struct A { struct InitHelper { InitHelper() { A::mA = "Hello, I'm A."; } }; static std::string mA; static InitHelper mInit; static const std::string& getA(){ return mA; } }; std::string A::mA; A::InitHelper A::mInit; template struct B { struct InitHelper { InitHelper() { B::mB = "Hello, I'm B."; // [3] } }; static std::string mB; static InitHelper mInit; static const std::string& getB() { return mB; } static InitHelper& getHelper(){ return mInit; } }; template std::string B::mB; //[4] template typename B::InitHelper B::mInit; int main(int argc, char* argv[]) { std::cout << "A = " << A::getA() << std::endl; // std::cout << "B = " << B::getB() << std::endl; // [1] // B::getHelper(); // [2] } 

Com g ++ 4.4.1:

Assim, minha pergunta: isso é um erro do compilador ou o bug está instalado entre o monitor e a cadeira? E se este for o caso: Existe uma solução elegante (ou seja, sem chamar explicitamente um método de boot estática)?

Atualizar I:
Este parece ser um comportamento desejado (conforme definido no padrão ISO / IEC C ++ 2003, 14.7.1):

A menos que um membro de um modelo de class ou um modelo de membro tenha sido explicitamente instanciado ou explicitamente especializado, a especialização do membro é implicitamente instanciada quando a especialização é referenciada em um contexto que requer que a definição de membro exista; em particular, a boot (e quaisquer efeitos colaterais associados) de um membro de dados estático não ocorre, a menos que o próprio membro de dados estáticos seja usado de uma maneira que exija a definição do membro de dados estáticos.

Isso foi discutido na usenet há algum tempo, enquanto eu tentava responder a outra pergunta no stackoverflow: Point of Instantiation de Static Data Members . Eu acho que vale a pena reduzir o caso de teste, e considerando cada cenário isoladamente, então vamos dar uma olhada mais geral primeiro:


 struct C { C(int n) { printf("%d\n", n); } }; template struct A { static C c; }; template C A::c(N); A<1> a; // implicit instantiation of A<1> and 2 A<2> b; 

Você tem a definição de um modelo de membro de dados estáticos. Isso ainda não cria membros de dados, devido a 14.7.1 :

“… em particular, a boot (e quaisquer efeitos colaterais associados) de um membro de dados estáticos não ocorre, a menos que o membro de dados estáticos seja usado de forma que exija a definição do membro de dados estáticos.”

A definição de algo (= entidade) é necessária quando essa entidade é “usada”, de acordo com a única regra de definição que define essa palavra (em 3.2/2 ). Em particular, se todas as referências forem de modelos não instanciados, membros de um modelo ou um sizeof expressões ou coisas semelhantes que não “usem” a entidade (já que eles não estão potencialmente avaliando-os, ou simplesmente não existem ainda como funções / membros que são usadas em si), tal membro de dados estático não é instanciado.

Uma instanciação implícita por 14.7.1/7 instancia declarações de membros de dados estáticos – isto é, instanciará qualquer modelo necessário para processar essa declaração. Não irá, no entanto, instanciar definições – isto é, inicializadores não são instanciados e construtores do tipo desse membro de dados estáticos não são implicitamente definidos (marcados como usados).

Isso tudo significa que o código acima não produzirá nada ainda. Vamos causar instanciações implícitas dos membros de dados estáticos agora.

 int main() { A<1>::c; // reference them A<2>::c; } 

Isso fará com que os dois membros de dados estáticos existam, mas a questão é: como está a ordem de boot? Em uma leitura simples, pode-se pensar que o 3.6.2/1 se aplica, o que diz (ênfase de mim):

“Objetos com duração de armazenamento estático definidos no escopo de namespace na mesma unidade de tradução e inicializados dinamicamente devem ser inicializados na ordem em que suas definições aparecem na unidade de tradução.”

Agora, como dito na postagem usenet e explicado neste relatório de defeitos , esses membros de dados estáticos não são definidos em uma unidade de tradução, mas são instanciados em uma unidade de instanciação , conforme explicado em 2.1/1 :

Cada unidade de tradução traduzida é examinada para produzir uma lista de instanciações necessárias. [Nota: isso pode include instanciações que foram explicitamente solicitadas (14.7.2). ] As definições dos modelos necessários estão localizadas. É definido pela implementação se a origem das unidades de tradução contendo essas definições deve estar disponível. [Nota: uma implementação pode codificar informações suficientes para a unidade de tradução traduzida, de modo a garantir que a fonte não seja necessária aqui. ] Todas as instanciações necessárias são executadas para produzir unidades de instanciação. [Nota: são semelhantes às unidades de tradução traduzidas, mas não contêm referências a modelos não instanciados nem definições de modelo. ] O programa é mal formado se qualquer instanciação falhar.

O Ponto de Instanciação de tal membro também não importa realmente, porque tal ponto de instanciação é o link de contexto entre uma instanciação e suas unidades de tradução – ele define as declarações que são visíveis (como especificado em 14.6.4.1 , e cada uma das esses pontos de instanciações devem dar às instanciações o mesmo significado, conforme especificado na única regra de definição em 3.2/5 , último caractere).

Se quisermos a boot ordenada, temos que organizar para não mexermos com instanciações, mas com declarações explícitas – essa é a área de especializações explícitas, já que elas não são realmente diferentes das declarações normais. Na verdade, o C ++ 0x alterou sua redação do 3.6.2 para o seguinte:

A boot dinâmica de um object não local com duração de armazenamento estático é ordenada ou não. As definições de membros de dados estáticos de modelo de class explicitamente especializados ordenaram a boot. Outros membros de dados estáticos do modelo de class (isto é, especializações instanciadas implícita ou explicitamente) têm boot não ordenada.


Isso significa para o seu código que:

  • [1] e [2] comentou: Nenhuma referência aos membros de dados estáticos existe, portanto suas definições (e também não suas declarações, já que não há necessidade de instanciação de B ) não são instanciadas. Nenhum efeito colateral ocorre.
  • [1] uncommented: B::getB() é usado, o que por si só usa B::mB , que requer que o membro estático exista. A string é inicializada antes do main (em qualquer caso antes dessa instrução, como parte da boot de objects não locais). Nada usa B::mInit , por isso não é instanciado e, portanto, nenhum object de B::InitHelper é criado, o que faz com que seu construtor não seja usado, que por sua vez nunca atribuirá algo a B::mB : Você só irá B::mB uma string vazia.
  • [1] e [2] uncommented: Isso funcionou para você é sorte (ou o contrário :)). Não há necessidade de uma ordem específica de chamadas de boot, conforme explicado acima. Pode funcionar no VC ++, falhar no GCC e funcionar no clang. Nós não sabemos
  • [1] commented, [2] uncommented: Same problem – novamente, ambos os membros de dados estáticos são usados : B::mInit é usado por B::getHelper , e a instanciação de B::mInit fará com que seu construtor seja instanciado, o que usará B::mB – mas para seu compilador, a ordem é diferente nesta execução específica (comportamento não especificado não precisa ser consistente entre execuções diferentes): Ele inicializa B::mInit primeiro, que operará em um object de string ainda não construído.

O problema é que as definições que você dá para as variables ​​de membro estático também são modelos.

 template std::string B::mB; template typename B::InitHelper B::mInit; 

Durante a compilation, isso não define nada, já que T não é conhecido. É algo como uma declaração de class ou uma definição de modelo, o compilador não gera código ou reserva de armazenamento quando o vê.

A definição acontece implicitamente mais tarde, quando você usa a class de modelo. Porque no caso de segfaulting você não usa B :: mInit, ele nunca é criado.

Uma solução seria definir explicitamente o membro necessário (sem inicializá-lo): Coloque em algum lugar um arquivo de origem

 template<> typename B::InitHelper B::mInit; 

Isso funciona basicamente da mesma maneira que definir explicitamente uma class de modelo.

  • [1] caso não comentado: está tudo bem. static InitHelper B::mInit não existe. Se o membro da class de modelo (struct) não for usado, ele não será compilado.

  • [1] e [2] caso não comentado: está tudo bem. B::getHelper() usa o static InitHelper B::mInit e mInit existe.

  • [1] comentou, [2] não comentou: funciona para mim no VS2008.