Por que um compilador não pode evitar importar um arquivo de cabeçalho duas vezes sozinho?

13

Novo no C ++! Então, eu estava lendo isso: http://www.learncpp.com/cpp-tutorial/110-a-first-look-at-the-preprocessor/

Protetores de cabeçalho

Como os arquivos de cabeçalho podem incluir outros arquivos de cabeçalho, é possível acabar na situação em que um arquivo de cabeçalho é incluído várias vezes.

Então, fazemos diretrizes de pré-processador para evitar isso. Mas não tenho certeza - por que o compilador não pode simplesmente ... não importar a mesma coisa duas vezes?

Dado que os protetores de cabeçalho são opcionais (mas aparentemente uma boa prática), quase me faz pensar que existem cenários em que você deseja importar algo duas vezes. Embora eu não consiga pensar em nenhum desses cenários. Alguma ideia?

Ómega
fonte
No compilador MS, existe o #pragma onceque diz ao compilador para incluir apenas esse arquivo uma vez.
CodesInChaos 29/03

Respostas:

27

Eles podem, como mostra os novos idiomas que o fazem.

Mas uma decisão de design foi tomada há muitos anos (quando o compilador C era múltiplo em estágios independentes) e agora, para manter a compatibilidade, o pré-processador precisa agir de uma certa maneira para garantir que o código antigo seja compilado conforme o esperado.

Como o C ++ herda a maneira como processa os arquivos de cabeçalho de C, ele manteve as mesmas técnicas. Estamos apoiando uma antiga decisão de design. Mas mudar a maneira como funciona é muito arriscado. Muitos códigos podem potencialmente quebrar. Então agora temos que ensinar aos novos usuários do idioma como usar os protetores de inclusão.

Existem alguns truques com os arquivos de cabeçalho nos quais você foi deliberadamente incluído várias vezes (isso realmente fornece um recurso útil). Embora, se redesenhamos o paradigma do zero, poderíamos tornar isso a maneira não padrão de incluir arquivos.

Martin York
fonte
7

Caso contrário, não seria tão expressivo, uma vez que eles escolheram manter a compatibilidade com C e, assim, continuar com um pré-processador em vez de um sistema de empacotamento tradicional.

Uma coisa que me vem à mente é que eu tinha um projeto que era uma API. Eu tinha dois arquivos de cabeçalho x86lib.he x86lib_internal.h. Como o interno era enorme, separei os bits "públicos" para x86lib.h, para que os usuários não precisassem reservar tempo extra para compilar.

Isso introduziu um problema engraçado com dependências, portanto, acabei tendo um fluxo que era assim no x86lib_internal

  1. Definir o pré-processador INTERNO definir
  2. Inclua x86lib.h (que foi inteligente para agir de uma certa maneira quando interno foi definido)
  3. Faça algumas coisas e introduza algumas coisas usadas no x86lib.h
  4. Definir DEPOIS de definir o pré-processador
  5. Inclua o x86lib.h novamente (desta vez, ele ignoraria tudo, exceto uma parte AFTER segregada, que dependia dos elementos do x86lib_internal

Eu não diria que era a melhor maneira de fazer isso, mas conseguiu o que eu queria.

Earlz
fonte
0

Uma dificuldade com a exclusão automática de cabeçalho duplicado é que o padrão C é relativamente silencioso quanto ao significado dos nomes de arquivos incluídos. Por exemplo, suponha que o arquivo principal que está sendo compilado contenha diretivas #include "f1.h"e #include "f2.h", e os arquivos encontrados para essas diretivas contenham #include "f3.h". Se f1.he f2.hestiver em diretórios diferentes, mas foram encontrados pela pesquisa de caminhos de inclusão, não seria claro que as #includediretivas desses arquivos foram destinadas a carregar o mesmo f3.harquivo ou diferentes.

As coisas pioram ainda mais se adicionarmos as possibilidades de incluir arquivos, incluindo caminhos relativos. Em alguns casos em que os arquivos de cabeçalho usam caminhos relativos para diretivas de inclusão aninhadas, e onde se deseja evitar alterações nos arquivos de cabeçalho fornecidos, pode ser necessário duplicar um arquivo de cabeçalho em vários locais na estrutura de diretórios de um projeto. Embora existam várias cópias físicas desse arquivo de cabeçalho, elas devem ser consideradas semanticamente como se fossem um único arquivo.

Se a #pragma oncediretiva permitisse que um identificador seguisse once, com a semântica que o compilador deve ignorar o arquivo se o identificador corresponder a um de uma #pragma oncediretiva encontrada anteriormente , a semântica seria inequívoca; um compilador que poderia dizer que uma #includediretiva carregaria o mesmo #pragma oncearquivo marcado como um anterior, poderia economizar um pouco de tempo ignorando o arquivo sem abri-lo novamente, mas essa detecção não seria semanticamente importante, pois o arquivo seria ignorado se ou não, o nome do arquivo foi reconhecido como uma correspondência. No entanto, não conheço nenhum compilador trabalhando dessa maneira. Ter um compilador observando se um arquivo corresponde ao padrão #ifndef someIdentifier / #define someIdentifier / #endif [for that ifndef] / nothing followinge tratando algo equivalente ao acima #pragma once someIdentifiersesomeIdentifier permanece definido, é essencialmente tão bom.

supercat
fonte