Partilhar via


Passo a passo: Criar e importar unidades de cabeçalho no Microsoft C++

Este artigo é sobre como criar e importar unidades de cabeçalho com o Visual Studio 2022. Para saber como importar cabeçalhos de biblioteca padrão C++ como unidades de cabeçalho, consulte Passo a passo: Importar bibliotecas STL como unidades de cabeçalho. Para obter uma maneira ainda mais rápida e robusta de importar a biblioteca padrão, consulte Tutorial: Importar a biblioteca padrão C++ usando módulos.

As unidades de cabeçalho são a alternativa recomendada aos arquivos de cabeçalho pré-compilados (PCH). As unidades de cabeçalho são mais fáceis de configurar e usar, são significativamente menores no disco, oferecem benefícios de desempenho semelhantes e são mais flexíveis do que uma PCH compartilhada.

Para contrastar unidades de cabeçalho com outras maneiras de incluir funcionalidade em seus programas, consulte Comparar unidades de cabeçalho, módulos e cabeçalhos pré-compilados.

Pré-requisitos

Para usar unidades de cabeçalho, você precisa do Visual Studio 2019 16.10 ou posterior.

O que é uma unidade de cabeçalho

Uma unidade de cabeçalho é uma representação binária de um arquivo de cabeçalho. Uma unidade de cabeçalho termina com a extensão .ifc. O mesmo formato é usado para módulos nomeados.

Uma diferença importante entre uma unidade de cabeçalho e um arquivo de cabeçalho é que uma unidade de cabeçalho não é afetada por definições de macro fora da unidade de cabeçalho. Ou seja, não é possível definir um símbolo de pré-processador que faça com que a unidade de cabeçalho se comporte de forma diferente. No momento em que você importa a unidade de cabeçalho, a unidade de cabeçalho já está compilada. Isso é diferente de como um #include arquivo é tratado. Um arquivo incluído pode ser afetado por uma definição de macro fora do arquivo de cabeçalho porque o arquivo de cabeçalho passa pelo pré-processador quando você compila o arquivo de origem que o inclui.

As unidades de cabeçalho podem ser importadas em qualquer ordem, o que não acontece com os arquivos de cabeçalho. A ordem do arquivo de cabeçalho é importante porque as definições de macro definidas em um arquivo de cabeçalho podem afetar um arquivo de cabeçalho subsequente. As definições de macro em uma unidade de cabeçalho não podem afetar outra unidade de cabeçalho.

Tudo o que é visível a partir de um ficheiro de cabeçalho também é visível a partir de uma unidade de cabeçalho, incluindo macros definidas dentro da unidade de cabeçalho.

Um arquivo de cabeçalho deve ser traduzido em uma unidade de cabeçalho antes de poder ser importado. Uma vantagem das unidades de cabeçalho sobre os arquivos de cabeçalho pré-compilados (PCH) é que eles podem ser usados em compilações distribuídas. Contanto que você compile o .ifc e o programa que o importa com o mesmo compilador e direcione a mesma plataforma e arquitetura, uma unidade de cabeçalho produzida em um computador pode ser consumida em outro. Ao contrário de uma PCH, quando uma unidade de cabeçalho muda, apenas ela e o que depende dela são reconstruídos. As unidades de cabeçalho podem ser até dez vezes menores do que um .pcharquivo.

As unidades de cabeçalho colocam menos restrições sobre as semelhanças necessárias entre as combinações de opções do compilador usadas para criar a unidade de cabeçalho e para compilar o código que a consome do que um PCH. No entanto, algumas combinações de switch e definições de macro podem criar violações da regra de uma definição (ODR) entre várias unidades de tradução.

Finalmente, as unidades de cabeçalho são mais flexíveis do que uma PCH. Com um PCH, você não pode optar por trazer apenas um dos cabeçalhos no PCH - o compilador processa todos eles. Com unidades de cabeçalho, mesmo quando você as compila juntas em uma biblioteca estática, você só traz o conteúdo da unidade de cabeçalho importada para seu aplicativo.

As unidades de cabeçalho são uma etapa entre os arquivos de cabeçalho e os módulos C++20. Eles fornecem alguns dos benefícios dos módulos. Eles são mais robustos porque as definições de macro externas não os afetam, então você pode importá-los em qualquer ordem. E o compilador pode processá-los mais rápido do que os arquivos de cabeçalho. Mas as unidades de cabeçalho não têm todas as vantagens dos módulos porque as unidades de cabeçalho expõem as macros definidas dentro delas (os módulos não). Ao contrário dos módulos, não há como ocultar a implementação privada em uma unidade de cabeçalho. Para indicar a implementação privada com arquivos de cabeçalho, diferentes técnicas são empregadas, como adicionar sublinhados principais a nomes ou colocar coisas em um namespace de implementação. Um módulo não expõe a implementação privada de forma alguma, então você não precisa fazer isso.

Considere substituir os cabeçalhos pré-compilados por unidades de cabeçalho. Você obtém a mesma vantagem de velocidade, mas com outros benefícios de higiene e flexibilidade de código também.

Maneiras de compilar uma unidade de cabeçalho

Há várias maneiras de compilar um arquivo em uma unidade de cabeçalho:

  • Crie um projeto de unidade de cabeçalho compartilhado. Recomendamos essa abordagem porque ela fornece mais controle sobre a organização e a reutilização das unidades de cabeçalho importadas. Crie um projeto de biblioteca estática que contenha as unidades de cabeçalho desejadas e, em seguida, faça referência a ele para importar as unidades de cabeçalho. Para obter um guia detalhado sobre esta abordagem, consulte Criar um projeto de biblioteca estática de unidade de cabeçalho para unidades de cabeçalho.

  • Escolha arquivos individuais para traduzir em unidades de cabeçalho. Essa abordagem oferece controle arquivo a arquivo sobre o que é tratado como uma unidade de cabeçalho. Também é útil quando você deve compilar um arquivo como uma unidade de cabeçalho que, por não ter a extensão padrão (.ixx, .cppm, .h, .hpp), normalmente não seria compilada em uma unidade de cabeçalho. Essa abordagem é demonstrada neste passo a passo. Para começar, consulte Abordagem 1: traduzir um arquivo específico em uma unidade de cabeçalho.

  • Analise e construa automaticamente unidades de cabeçalho. Essa abordagem é conveniente, mas é mais adequada para projetos menores porque não garante uma eficiência na construção ideal. Para obter detalhes sobre essa abordagem, consulte Abordagem 2: Verificar automaticamente unidades de cabeçalho.

  • Como mencionado na introdução, você pode criar e importar arquivos de cabeçalho STL como unidades de cabeçalho e tratar #include automaticamente os cabeçalhos de biblioteca STL como import sem reescrever seu código. Para ver como, visite Passo a passo: Importar bibliotecas STL como unidades de cabeçalho.

Abordagem 1: Traduzir um arquivo específico em uma unidade de cabeçalho

Esta seção mostra como escolher um arquivo específico para traduzir em uma unidade de cabeçalho. Compile um arquivo de cabeçalho como uma unidade de cabeçalho usando as seguintes etapas no Visual Studio:

  1. Crie um novo projeto de aplicativo de console C++.

  2. Substitua o conteúdo do arquivo de origem da seguinte maneira:

    #include "Pythagorean.h"
    
    int main()
    {
        PrintPythagoreanTriple(2,3);
        return 0;
    }
    
  3. Adicione um arquivo de cabeçalho chamado Pythagorean.h e, em seguida, substitua seu conteúdo por este código:

    #ifndef PYTHAGOREAN
    #define PYTHAGOREAN
    
    #include <iostream>
    
    inline void PrintPythagoreanTriple(int a, int b)
    {
        std::cout << "Pythagorean triple a:" << a << " b:" << b << " c:" << a*a + b*b << std::endl;
    }
    #endif
    

Definir propriedades do projeto

Para habilitar unidades de cabeçalho, primeiro defina o padrão de linguagem C++ para /std:c++20 ou posterior com as seguintes etapas:

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no nome do projeto e escolha Propriedades.
  2. No painel esquerdo da janela de páginas de propriedades do projeto, selecione Propriedades>de configuração Geral.
  3. No menu suspenso Padrão de linguagem C++ , selecione ISO C++20 Standard (/std:c++20) ou posterior. Escolha Ok para fechar a caixa de diálogo.

Compile o arquivo de cabeçalho como uma unidade de cabeçalho:

  1. No Gerenciador de Soluções, selecione o arquivo que você deseja compilar como uma unidade de cabeçalho (neste caso, Pythagorean.h). Clique com o botão direito do mouse no arquivo e escolha Propriedades.

  2. Defina as Propriedades de Configuração>Geral>Tipo de Item da lista suspensa para compilador C/C++ e escolha Ok.

    Captura de tela que mostra a alteração do tipo de item para compilador C/C++.

Quando este projeto for compilado mais tarde neste passo a passo, Pythagorean.h será traduzido para uma unidade de cabeçalho. Ele é traduzido em uma unidade de cabeçalho porque o tipo de item para esse arquivo de cabeçalho é definido como compilador C/C++ e porque a ação padrão para .h e .hpp arquivos definidos dessa maneira é traduzir o arquivo em uma unidade de cabeçalho.

Observação

Isso não é necessário para este passo a passo, mas é fornecido para suas informações. Para compilar um ficheiro como uma unidade de cabeçalho que não tem uma extensão de ficheiro de unidade de cabeçalho padrão, como .cpp por exemplo, defina as propriedades de configuração>C/C++>Avançadas>Compilar Como para Compilar como Unidade de Cabeçalho C++ (/exportHeader): Captura de ecrã que mostra a alteração das propriedades de configuração > C/C++ > Avançadas > Compilar Como para Compilar como Unidade de Cabeçalho C++ (/exportHeader).

Altere o código para importar a unidade de cabeçalho

  1. No arquivo de origem do projeto de exemplo, altere #include "Pythagorean.h" para import "Pythagorean.h";. Não se esqueça do ponto-e-vírgula à direita. É obrigatório para import declarações. Como se trata de um ficheiro de cabeçalho numa diretoria local do projeto, utilizámos aspas com a instrução import: import "file";. Em seus próprios projetos, para compilar uma unidade de cabeçalho a partir de um cabeçalho do sistema, use colchetes angulares: import <file>;

  2. Crie a solução selecionando Build>Build Solution no menu principal. Execute-o para ver se ele produz a saída esperada: Pythagorean triple a:2 b:3 c:13

Em seus próprios projetos, repita esse processo para compilar os arquivos de cabeçalho que você deseja importar como unidades de cabeçalho.

Se você quiser converter apenas alguns arquivos de cabeçalho em unidades de cabeçalho, essa abordagem é boa. Mas se você tiver muitos arquivos de cabeçalho que deseja compilar e a perda potencial de desempenho de compilação for superada pela conveniência de ter o sistema de compilação manipulando-os automaticamente, consulte a seção a seguir.

Se você estiver interessado em importar especificamente cabeçalhos de biblioteca STL como unidades de cabeçalho, consulte Passo a passo: Importar bibliotecas STL como unidades de cabeçalho.

Abordagem 2: Procurar e construir unidades de cabeçalho automaticamente

Como leva tempo para verificar todos os seus arquivos de origem em busca de unidades de cabeçalho e tempo para criá-los, a abordagem a seguir é mais adequada para projetos menores. Não garante um desempenho de compilação ideal.

Essa abordagem combina duas configurações de projeto do Visual Studio:

  • Scan Sources for Module Dependencies faz com que o sistema de compilação chame o compilador para garantir que todos os módulos importados e unidades de cabeçalho sejam criados antes de compilar os arquivos que dependem deles. Quando combinado com Translate Includes to Imports, qualquer arquivo de cabeçalho incluído na sua fonte que também seja especificado num arquivo header-units.json localizado no mesmo diretório do arquivo de cabeçalho, é compilado em unidades de cabeçalho.
  • Translate Includes to Imports trata um arquivo de cabeçalho como um import se o #include se refere a um arquivo de cabeçalho que pode ser compilado como uma unidade de cabeçalho (conforme especificado em um header-units.json arquivo), e uma unidade de cabeçalho compilada está disponível para o arquivo de cabeçalho. Caso contrário, o arquivo de cabeçalho é tratado como um #include. O header-units.json arquivo é usado para construir automaticamente unidades de cabeçalho para cada #include, sem duplicação de símbolos.

Você pode ativar essas configurações nas propriedades do seu projeto. Para fazer isso, clique com o botão direito do mouse no projeto no Gerenciador de Soluções e escolha Propriedades. Em seguida, escolha Configuration Properties>C/C++>General.

Captura de tela que mostra a tela de propriedades do projeto com Configuração realçada e Todas as configurações selecionadas. Em C/C++ > Geral, Scan Sources for Module Dependencies é realçado e definido como yes, e Translate Includes to Imports é realçado e definido como Yes (/translateInclude)

As fontes de verificação para dependências de módulo podem ser definidas para todos os arquivos no projeto em Propriedades do projeto , conforme mostrado aqui, ou para arquivos individuais em Propriedades do arquivo. Os módulos e unidades de cabeçalho são sempre verificados. Defina essa opção quando tiver um .cpp arquivo que importe unidades de cabeçalho que você deseja criar automaticamente e que talvez ainda não tenha sido compilado.

Essas configurações funcionam juntas para criar e importar automaticamente unidades de cabeçalho sob estas condições:

  • Scan Sources for Module Dependencies verifica suas fontes em busca dos arquivos e suas dependências que podem ser tratados como unidades de cabeçalho. Os arquivos que têm a extensão .ixx, e os arquivos que têm as suas propriedades de Ficheiro>C/C++>Compilar como definidas como Compile as C++ Header Unit (/export), são sempre analisados, independentemente dessa configuração. O compilador também procura import declarações para identificar dependências da unidade de cabeçalho. Se /translateInclude for especificado, o compilador também verificará por diretivas #include que estão também especificadas em um arquivo header-units.json para tratar como unidades de cabeçalho. Um gráfico de dependência é construído de todos os módulos e unidades de cabeçalho em seu projeto.
  • Traduzir Includes para Imports Quando o compilador encontra uma #include instrução, e existe um ficheiro de unidade de cabeçalho correspondente (.ifc) para o ficheiro de cabeçalho especificado, o compilador importa a unidade de cabeçalho em vez de tratar o ficheiro de cabeçalho como um #include. Quando combinado com Scan for dependencies, o compilador encontra todos os arquivos de cabeçalho que podem ser compilados em unidades de cabeçalho. Uma lista de permissões é consultada pelo compilador para decidir quais arquivos de cabeçalho podem ser compilados em unidades de cabeçalho. Essa lista é armazenada em um header-units.json arquivo que deve estar no mesmo diretório que o arquivo incluído. Você pode ver um exemplo de um header-units.json arquivo no diretório de instalação do Visual Studio. Por exemplo, %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json é usado pelo compilador para determinar se um cabeçalho da Biblioteca de Modelos Padrão pode ser compilado em uma unidade de cabeçalho. Esta função existe para servir como uma ponte com o código herdado para tirar partido de alguns benefícios das unidades de cabeçalho.

O header-units.json ficheiro tem duas finalidades. Além de especificar quais arquivos de cabeçalho podem ser compilados em unidades de cabeçalho, ele minimiza símbolos duplicados para aumentar a taxa de transferência de compilação. Para obter mais informações sobre duplicação de símbolos, consulte C++ header-units.json reference.

Estes interruptores e o header-unit.json fornecem alguns dos benefícios das unidades de cabeçalho. A conveniência tem o custo do desempenho de construção. Essa abordagem pode não ser a melhor para projetos maiores, porque não garante tempos de construção ideais. Além disso, os mesmos arquivos de cabeçalho podem ser reprocessados repetidamente, o que aumenta o tempo de compilação. No entanto, a conveniência pode valer a pena, dependendo do projeto.

Esses recursos são projetados para código legado. Para código novo, mova para módulos em vez de unidades de cabeçalho ou arquivos #include. Para obter um tutorial sobre como usar módulos, consulte Tutorial de módulos de nome (C++).

Para obter um exemplo de como essa técnica é usada para importar arquivos de cabeçalho STL como unidades de cabeçalho, consulte Passo a passo: Importar bibliotecas STL como unidades de cabeçalho.

Implicações do pré-processador

O pré-processador padrão em conformidade com C99/C++11 é necessário para criar e usar unidades de cabeçalho. O compilador habilita o novo pré-processador em conformidade com C99/C++11 ao compilar unidades de cabeçalho, adicionando /Zc:preprocessor implicitamente à linha de comando sempre que qualquer forma de /exportHeader é usada. Tentar desativá-lo resultará em um erro de compilação.

A ativação do novo pré-processador afeta o processamento de macros variádicas. Para mais informações, consulte a secção de comentários sobre macros variádicas.

Ver também

/translateInclude
/exportHeader
/headerUnit
header-units.json
Comparar unidades de cabeçalho, módulos e cabeçalhos pré-compilados
Visão geral dos módulos em C++
Tutorial: Importar a biblioteca padrão C++ usando módulos
Passo a passo: Importar bibliotecas STL como unidades de cabeçalho