Quais são os benefícios de uma biblioteca apenas de cabeçalho e por que você a escreveria dessa forma se opondo a colocar a implementação em um arquivo separado?
Quais são os benefícios de uma biblioteca apenas de cabeçalho e por que você a escreveria dessa forma se opondo a colocar a implementação em um arquivo separado?
Respostas:
Existem situações em que uma biblioteca apenas de cabeçalho é a única opção, por exemplo, ao lidar com modelos.
Ter uma biblioteca apenas de cabeçalho também significa que você não precisa se preocupar com as diferentes plataformas onde a biblioteca pode ser usada. Quando você separa a implementação, geralmente o faz para ocultar os detalhes da implementação e distribuir a biblioteca como uma combinação de cabeçalhos e bibliotecas ( lib
, dll
's ou .so
arquivos). É claro que eles devem ser compilados para todos os sistemas operacionais / versões diferentes para os quais você oferece suporte.
Você também pode distribuir os arquivos de implementação, mas isso significaria uma etapa extra para o usuário - compilar sua biblioteca antes de usá-la.
Claro, isso se aplica caso a caso . Por exemplo, bibliotecas apenas de cabeçalho às vezes aumentamtamanho do código e tempos de compilação.
Benefícios da biblioteca somente de cabeçalho:
Desvantagens de uma biblioteca apenas de cabeçalho:
Arquivos de objetos maiores. Cada método embutido da biblioteca que é usado em algum arquivo de origem também obterá um símbolo fraco, definição fora de linha no arquivo de objeto compilado para esse arquivo de origem. Isso torna o compilador mais lento e também o vinculador. O compilador precisa gerar todo esse inchaço e, em seguida, o vinculador precisa filtrá-lo.
Compilação mais longa. Além do problema de inchaço mencionado acima, a compilação demorará mais porque os cabeçalhos são inerentemente maiores com uma biblioteca apenas de cabeçalho do que com uma biblioteca compilada. Esses cabeçalhos grandes precisarão ser analisados para cada arquivo de origem que usa a biblioteca. Outro fator é que os arquivos de cabeçalho em uma biblioteca somente de #include
cabeçalho têm os cabeçalhos necessários para as definições embutidas, bem como os cabeçalhos que seriam necessários se a biblioteca fosse construída como uma biblioteca compilada.
Compilação mais confusa. Você obtém muito mais dependências com uma biblioteca somente de cabeçalho por causa daqueles #include
s extras necessários com uma biblioteca somente de cabeçalho. Altere a implementação de alguma função-chave na biblioteca e talvez seja necessário recompilar o projeto inteiro. Faça essa alteração no arquivo de origem de uma biblioteca compilada e tudo o que você precisa fazer é recompilar esse arquivo de origem da biblioteca, atualizar a biblioteca compilada com esse novo arquivo .o e vincular novamente o aplicativo.
Mais difícil para o humano ler. Mesmo com a melhor documentação, os usuários de uma biblioteca muitas vezes têm que recorrer à leitura dos cabeçalhos da biblioteca. Os cabeçalhos em uma biblioteca apenas de cabeçalho são preenchidos com detalhes de implementação que atrapalham a compreensão da interface. Com uma biblioteca compilada, tudo o que você vê é a interface e um breve comentário sobre o que a implementação faz, e isso geralmente é tudo o que você deseja. Isso é realmente tudo que você deveria querer. Você não precisa saber os detalhes de implementação para saber como usar a biblioteca.
detail
.
Eu sei que este é um tópico antigo, mas ninguém mencionou interfaces ABI ou problemas específicos do compilador. Então eu pensei que faria.
Isso é basicamente baseado no conceito de você escrever uma biblioteca com um cabeçalho para distribuir às pessoas ou reutilizar você mesmo em vez de ter tudo em um cabeçalho. Se você está pensando em reutilizar um cabeçalho e arquivos de origem e recompilá-los em todos os projetos, isso não se aplica.
Basicamente, se você compilar seu código C ++ e construir uma biblioteca com um compilador, o usuário tenta usar essa biblioteca com um compilador diferente ou uma versão diferente do mesmo compilador, então você pode obter erros de vinculador ou comportamento de tempo de execução estranho devido à incompatibilidade binária.
Por exemplo, os fornecedores de compiladores costumam alterar sua implementação da STL entre as versões. Se você tem uma função em uma biblioteca que aceita um std :: vector, ela espera que os bytes dessa classe sejam organizados da maneira como foram organizados quando a biblioteca foi compilada. Se, em uma nova versão do compilador, o fornecedor fez melhorias de eficiência em std :: vector, então o código do usuário vê a nova classe que pode ter uma estrutura diferente e passa essa nova estrutura para sua biblioteca. Tudo vai piorando a partir daí ... É por isso que é recomendado não passar objetos STL além dos limites da biblioteca. O mesmo se aplica aos tipos C Run-Time (CRT).
Ao falar sobre o CRT, sua biblioteca e o código-fonte do usuário geralmente precisam ser vinculados ao mesmo CRT. Com o Visual Studio, se você construir sua biblioteca usando o CRT multithread, mas os links do usuário contra o CRT de depuração multithreaded, você terá problemas de link porque sua biblioteca pode não encontrar os símbolos de que precisa. Não me lembro qual era a função, mas para o Visual Studio 2015 a Microsoft criou uma função CRT embutida. De repente, ele não estava no cabeçalho da biblioteca CRT, de modo que as bibliotecas que esperavam encontrá-lo no momento do link não puderam mais fazê-lo e isso gerou erros de link. O resultado foi que essas bibliotecas precisaram ser recompiladas com o Visual Studio 2015.
Você também pode obter erros de link ou comportamento estranho se usar a API do Windows, mas criar com configurações Unicode diferentes para o usuário da biblioteca. Isso ocorre porque a API do Windows tem funções que usam strings e macros Unicode ou ASCII / define quais usam automaticamente os tipos corretos com base nas configurações Unicode do projeto. Se você passar uma string pelo limite da biblioteca que seja do tipo errado, as coisas quebram no tempo de execução. Ou você pode descobrir que o programa não cria links em primeiro lugar.
Essas coisas também são verdadeiras para passar objetos / tipos através dos limites da biblioteca de outras bibliotecas de terceiros (por exemplo, um vetor Eigen ou uma matriz GSL). Se a biblioteca de terceiros alterar seu cabeçalho entre a compilação da biblioteca e a compilação do código pelo usuário, as coisas não funcionarão.
Basicamente, para garantir a segurança, as únicas coisas que você pode ultrapassar os limites da biblioteca são os tipos incorporados e Plain Old Data (POD). Idealmente, qualquer POD deve estar em estruturas definidas em seus próprios cabeçalhos e não depender de cabeçalhos de terceiros.
Se você fornecer uma biblioteca apenas de cabeçalho, todo o código será compilado com as mesmas configurações do compilador e com os mesmos cabeçalhos, de modo que muitos desses problemas desaparecem (fornecendo a versão de terceiros parcialmente bibliotecas que você e seu usuário usa são compatíveis com API).
No entanto, existem aspectos negativos que foram mencionados acima, como o aumento do tempo de compilação. Além disso, você pode estar administrando uma empresa, portanto, não pode querer entregar todos os detalhes de implementação do seu código-fonte a todos os seus usuários, caso um deles o roube.
O principal "benefício" é que requer que você forneça o código-fonte, então você acabará com relatórios de erros em máquinas e com compiladores dos quais nunca ouviu falar. Quando a biblioteca é inteiramente de modelos, você não tem muita escolha, mas quando você tem a escolha, apenas o cabeçalho é geralmente uma escolha de engenharia ruim. (Por outro lado, é claro, o cabeçalho significa apenas que você não precisa documentar nenhum procedimento de integração.)
Inlining pode ser feito por Link Time Optimization (LTO)
Gostaria de destacar isso, pois diminui o valor de uma das duas principais vantagens das bibliotecas somente de cabeçalho: "você precisa de definições em um cabeçalho para embutir".
Um exemplo concreto mínimo disso é mostrado em: Link-time optimization and inline
Então, você apenas passa um sinalizador e o inlining pode ser feito em arquivos de objetos sem nenhum trabalho de refatoração, não há mais necessidade de manter as definições nos cabeçalhos para isso.
LTO pode ter suas próprias desvantagens também, no entanto: há uma razão para não usar a otimização de tempo de link (LTO)?