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:
[1] e [2] comentaram:
A = Olá, sou A.
Funciona como pretendido
[1] sem comentário:
A = Olá, sou A. B =
Eu esperaria que o InitHelper inicializasse mB
A = Olá, sou A. B = Olá, eu sou B.
Funciona como pretendido
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
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
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
usa o static InitHelper B
e mInit
existe.
[1] comentou, [2] não comentou: funciona para mim no VS2008.