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 arquivo a.pas
como este:
unit a;
interface
var MyStaticVariable: Integer;
implementation
begin
MyStaticVariable := 0
end.
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
):
unit b;
interface
uses a;
procedure PrintB;
implementation
procedure PrintB;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
e uma unidade c ( c.pas
):
unit c;
interface
uses a;
procedure PrintC;
implementation
procedure PrintC;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
Finalmente, você pode usar as unidades bec em um programa principal m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
Você pode compilar esses arquivos separadamente:
$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas
e depois produza um executável com:
$ gpc -o m m.o a.o b.o c.o
e execute:
$ ./m
1
2
3
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 global MyStaticVariable
é definido apenas uma vez no arquivo de objeto a.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.