class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
Não vejo a necessidade de ter A::x
definido separadamente em um arquivo .cpp (ou o mesmo arquivo para modelos). Por que não pode ser A::x
declarado e definido ao mesmo tempo?
Foi proibido por razões históricas?
Minha principal pergunta é: isso afetará alguma funcionalidade se static
os membros de dados forem declarados / definidos ao mesmo tempo (o mesmo que Java )?
c++
data
language-features
grammar
static-access
iammilind
fonte
fonte
inline static int x[] = {1, 2, 3};
. Veja en.cppreference.com/w/cpp/language/static#Static_data_membersRespostas:
Eu acho que a limitação que você considerou não está relacionada à semântica (por que algo deve mudar se a inicialização foi definida no mesmo arquivo?), Mas ao modelo de compilação C ++ que, por razões de compatibilidade com versões anteriores, não pode ser facilmente alterado porque ou se tornam muito complexos (suportando um novo modelo de compilação e o existente ao mesmo tempo) ou não permitem compilar o código existente (introduzindo um novo modelo de compilação e descartando o existente).
O modelo de compilação C ++ deriva do modelo C, no qual você importa declarações para um arquivo de origem, incluindo arquivos (cabeçalho). Dessa forma, o compilador vê exatamente um grande arquivo de origem, contendo todos os arquivos incluídos e todos os arquivos incluídos nesses arquivos, recursivamente. Isso tem uma grande vantagem da IMO, a saber, que facilita a implementação do compilador. Obviamente, você pode escrever qualquer coisa nos arquivos incluídos, como declarações e definições. É apenas uma boa prática colocar declarações em arquivos de cabeçalho e definições em arquivos .c ou .cpp.
Por outro lado, é possível ter um modelo de compilação no qual o compilador saiba muito bem se está importando a declaração de um símbolo global definido em outro módulo ou se está compilando a definição de um símbolo global fornecida por o módulo atual . Somente neste último caso, o compilador deve colocar esse símbolo (por exemplo, uma variável) no arquivo de objeto atual.
Por exemplo, no GNU Pascal, você pode escrever uma unidade
a
em um arquivoa.pas
como este:onde a variável global é declarada e inicializada no mesmo arquivo de origem.
Então você pode ter unidades diferentes que importam a e usam a variável global
MyStaticVariable
, por exemplo, uma unidade b (b.pas
):e uma unidade c (
c.pas
):Finalmente, você pode usar as unidades bec em um programa principal
m.pas
:Você pode compilar esses arquivos separadamente:
e depois produza um executável com:
e execute:
O truque aqui é que, quando o compilador vê uma diretiva usos em um módulo de programa (por exemplo, usa a em b.pas), ele não inclui o arquivo .pas correspondente, mas procura um arquivo .gpi, ou seja, um arquivo pré-compilado arquivo de interface (consulte a documentação ). Esses
.gpi
arquivos são gerados pelo compilador junto com os.o
arquivos quando cada módulo é compilado. Portanto, o símbolo globalMyStaticVariable
é definido apenas uma vez no arquivo de objetoa.o
.Java funciona de maneira semelhante: quando o compilador importa uma classe A para a classe B, ele procura no arquivo de classe A e não precisa do arquivo
A.java
. Portanto, todas as definições e inicializações da classe A podem ser colocadas em um arquivo de origem.Voltando ao C ++, a razão pela qual no C ++ é necessário definir membros de dados estáticos em um arquivo separado está mais relacionada ao modelo de compilação do C ++ do que às limitações impostas pelo vinculador ou por outras ferramentas usadas pelo compilador. No C ++, importar alguns símbolos significa criar sua declaração como parte da unidade de compilação atual. Isso é muito importante, entre outras coisas, devido à maneira como os modelos são compilados. Mas isso implica que você não pode / não deve definir nenhum símbolo global (funções, variáveis, métodos, membros de dados estáticos) em um arquivo incluído; caso contrário, esses símbolos podem ser definidos de várias formas nos arquivos de objetos compilados.
fonte
Como os membros estáticos são compartilhados entre TODAS as instâncias de uma classe, eles precisam ser definidos em um e apenas um local. Realmente, são variáveis globais com algumas restrições de acesso.
Se você tentar defini-los no cabeçalho, eles serão definidos em todos os módulos que incluem esse cabeçalho e você receberá erros durante a vinculação, pois encontra todas as definições duplicadas.
Sim, isso é pelo menos em parte uma questão histórica que data do cfront; um compilador pode ser escrito para criar uma espécie de "static_members_of_everything.cpp" oculto e vincular a isso. No entanto, isso quebraria a compatibilidade com versões anteriores e não haveria nenhum benefício real em fazê-lo.
fonte
static
variáveis são declaradas / definidas no mesmo local (como Java), o que pode dar errado?static
membrostemplate
? Eles são permitidos em todos os arquivos de cabeçalho, pois precisam estar visíveis. Não estou contestando esta resposta, mas ela também não corresponde à minha pergunta.O motivo provável para isso é que isso mantém a linguagem C ++ implementável em ambientes onde o arquivo de objetos e o modelo de ligação não suportam a mesclagem de várias definições de vários arquivos de objetos.
Uma declaração de classe (chamada de declaração por boas razões) é puxada para várias unidades de tradução. Se a declaração contivesse definições para variáveis estáticas, você terminaria com várias definições em várias unidades de tradução (e lembre-se, esses nomes têm ligação externa).
Essa situação é possível, mas requer que o vinculador lide com várias definições sem reclamar.
(E observe que isso está em conflito com a Regra de Uma Definição, a menos que possa ser feita de acordo com o tipo de símbolo ou em que tipo de seção é inserida.)
fonte
Há uma grande diferença entre C ++ e Java.
Java opera em sua própria máquina virtual que cria tudo em seu próprio ambiente de tempo de execução. Se uma definição for vista mais de uma vez, ela simplesmente atuará no mesmo objeto que o ambiente de tempo de execução conhece ultimamente.
No C ++, não há "proprietário do conhecimento final": C ++, C, Fortran Pascal etc. são todos "tradutores" de um código-fonte (arquivo CPP) para um formato intermediário (o arquivo OBJ ou o arquivo ".o", dependendo do OS) onde as instruções são traduzidas em instruções de máquina e os nomes se tornam endereços indiretos mediados por uma tabela de símbolos.
Um programa não é feito pelo compilador, mas por outro programa (o "vinculador"), que une todos os OBJ-s (independentemente do idioma de origem), apontando novamente todos os endereços direcionados a símbolos, em direção a seus definição efetiva.
Pela maneira como o vinculador funciona, uma definição (o que cria o espaço físico para uma variável) deve ser exclusiva.
Observe que o C ++ não é vinculado por si só e que o vinculador não é emitido pelas especificações do C ++: o vinculador existe devido à maneira como os módulos do SO são criados (geralmente em C e ASM). C ++ tem que usá-lo do jeito que está.
Agora: um arquivo de cabeçalho é algo para ser "colado" em vários arquivos CPP. Todo arquivo CPP é traduzido independentemente de qualquer outro. Um compilador traduzindo diferentes arquivos CPP, todos recebendo uma mesma definição colocará o " código de criação " do objeto definido em todos os OBJs resultantes.
O compilador não sabe (e nunca saberá) se todos esses OBJs serão usados juntos para formar um único programa ou separadamente para formar diferentes programas independentes.
O vinculador não sabe como e por que as definições existem e de onde elas vêm (nem sequer conhece C ++: toda "linguagem estática" pode produzir definições e referências a serem vinculadas). Apenas sabe que existem referências a um determinado "símbolo" que é "definido" em um determinado endereço resultante.
Se houver várias definições (não confunda definições com referências) para um determinado símbolo, o vinculador não tem conhecimento (sendo independente da linguagem) sobre o que fazer com eles.
É como fundir uma cidade para formar uma cidade grande: se você encontrar duas " Time Square " e várias pessoas vindas de fora pedindo para ir para " Time Square ", não poderá decidir com base pura técnica. (sem nenhum conhecimento sobre a política que atribuiu esses nomes e será responsável por gerenciá-los) no local exato para enviá-los.
fonte
É necessário porque, caso contrário, o compilador não sabe onde colocar a variável. Cada arquivo cpp é compilado individualmente e não conhece o outro. O vinculador resolve variáveis, funções etc. Eu pessoalmente não vejo qual a diferença entre os membros vtable e static (não precisamos escolher em qual arquivo o vtable está definido).
Suponho principalmente que é mais fácil para escritores de compiladores implementá-lo dessa maneira. Vars estáticos fora da classe / estrutura existem e talvez por motivos de consistência ou porque seria "mais fácil de implementar" para os escritores de compiladores que eles definiram essa restrição nos padrões.
fonte
Eu acho que encontrei o motivo. Definir
static
variável em espaço separado permite inicializá-lo para qualquer valor. Se não inicializado, será padronizado como 0.Antes do C ++ 11, a inicialização na classe não era permitida no C ++. Portanto, não se pode escrever como:
Portanto, agora para inicializar a variável, é necessário escrevê-la fora da classe como:
Como discutido em outras respostas também,
int X::i
agora é global e declarar global em muitos arquivos causa erro no link de vários símbolos.Assim, é preciso declarar uma
static
variável de classe dentro de uma unidade de tradução separada. No entanto, ainda pode-se argumentar que a seguinte maneira deve instruir o compilador a não criar vários símbolosfonte
A :: x é apenas uma variável global, mas com namespace para A e com restrições de acesso.
Alguém ainda precisa declará-lo, como qualquer outra variável global, e isso pode até ser feito em um projeto que está estaticamente vinculado ao projeto que contém o restante do código A.
Eu chamaria isso de design ruim, mas há alguns recursos que você pode explorar dessa maneira:
a ordem de chamada do construtor ... Não é importante para um int, mas para um membro mais complexo que talvez acesse outras variáveis estáticas ou globais, isso pode ser crítico.
o inicializador estático - você pode permitir que um cliente decida em que A :: x deve ser inicializado.
em c ++ e c, como você tem acesso total à memória por meio de ponteiros, a localização física das variáveis é significativa. Há coisas muito impertinentes que você pode explorar com base em onde uma variável está localizada em um objeto de link.
Duvido que seja por isso que essa situação tenha surgido. Provavelmente é apenas uma evolução do C se transformando em C ++ e um problema de compatibilidade com versões anteriores que impede você de alterar o idioma agora.
fonte