Share via


Stap-voor-stap handleiding: Header-eenheden bouwen en importeren in Microsoft C++

Dit artikel gaat over het bouwen en importeren van header-eenheden met Visual Studio 2022. Zie Walkthrough: STL-bibliotheken importeren als header units voor meer informatie over het importeren van C++-standaardbibliotheekheaders als header units. Zie Zelfstudie: De C++-standaardbibliotheek importeren met behulp van modules voor een nog snellere en krachtigere manier om de standaardbibliotheek van C++ te importeren.

Header-eenheden zijn het aanbevolen alternatief voor vooraf gecompileerde headerbestanden (PCH). Header-eenheden zijn eenvoudiger in te stellen en te gebruiken, zijn aanzienlijk kleiner op schijf, bieden vergelijkbare prestatievoordelen en zijn flexibeler dan een gedeelde PCH.

Zie Kopteksteenheden, modules en vooraf gecompileerde headers vergelijken om kopteksteenheden te contrasteren met andere manieren om functionaliteit op te nemen in uw programma's.

Vereiste voorwaarden

Om header-eenheden te gebruiken, hebt u Visual Studio 2019 16.10 of hoger nodig.

Wat is een koptekstmodule?

Een header-eenheid is een binaire weergave van een headerbestand. Een header-eenheid eindigt met een .ifc extensie. Dezelfde indeling wordt gebruikt voor benoemde modules.

Een belangrijk verschil tussen een kopteksteenheid en een koptekstbestand is dat een kopteksteenheid niet wordt beïnvloed door macrodefinities buiten de kopteksteenheid. Dat wil gezegd, u kunt geen preprocessorsymbool definiëren dat ervoor zorgt dat de header-eenheid zich anders gedraagt. Op het moment dat u de header-eenheid importeert, wordt de header-eenheid al gecompileerd. Dat verschilt van de manier waarop een #include bestand wordt behandeld. Een opgenomen bestand kan worden beïnvloed door een macrodefinitie buiten het koptekstbestand, omdat het headerbestand de preprocessor doorloopt wanneer u het bronbestand compileert dat het bevat.

Koppeneenheden kunnen in elke gewenste volgorde worden geïmporteerd, terwijl dat niet geldt voor headerbestanden. De volgorde van koptekstbestanden is van belang omdat macrodefinities die zijn gedefinieerd in één koptekstbestand van invloed kunnen zijn op een volgend headerbestand. Macrodefinities in één kopteksteenheid kunnen geen invloed hebben op een andere kopteksteenheid.

Alles wat zichtbaar is vanuit een koptekstbestand, is ook zichtbaar vanuit een kopteksteenheid, inclusief macro's die zijn gedefinieerd in de kopteksteenheid.

Een headerbestand moet worden vertaald in een header unit voordat het kan worden geïmporteerd. Een voordeel van header-eenheden ten opzichte van vooraf gecompileerde headerbestanden (PCH) is dat ze kunnen worden gebruikt in gedistribueerde builds. Zolang u het .ifc en het programma dat het importeert compileert met dezelfde compiler en op hetzelfde platform en dezelfde architectuur richt, kan een header-eenheid die op de ene computer wordt geproduceerd, op een andere worden gebruikt. In tegenstelling tot een PCH, wanneer een header-eenheid wordt gewijzigd, worden alleen deze en wat ervan afhankelijk is, opnieuw opgebouwd. Header-eenheden kunnen tot een factor van tien kleiner zijn dan een .pch.

Header-eenheden opleggen minder beperkingen voor de vereiste overeenkomsten van combinaties van compilerswitchs die worden gebruikt om de header-eenheid te maken en om de code te compileren die deze verbruikt dan een PCH. Sommige schakelcombinaties en macrodefinities kunnen echter schendingen van de ene definitieregel (ODR) tussen verschillende vertaaleenheden tot stand brengen.

Ten slotte zijn header-eenheden flexibeler dan een PCH. Met een PCH kunt u er niet voor kiezen om slechts één van de headers in de PCH binnen te halen. De compiler verwerkt ze allemaal. Met header-eenheden, zelfs wanneer u ze samen compileert in een statische bibliotheek, neemt u alleen de inhoud mee van de header-eenheid die u in uw toepassing importeert.

Header-eenheden zijn een tussenstap tussen headerbestanden en C++20-modules. Ze bieden enkele voordelen van modules. Ze zijn robuuster omdat externe macrodefinities deze niet beïnvloeden, zodat u ze in elke volgorde kunt importeren. En de compiler kan ze sneller verwerken dan headerbestanden. Maar headers hebben niet alle voordelen van modules omdat headers de macro's die erin zijn gedefinieerd blootstellen (modules niet). In tegenstelling tot modules is het niet mogelijk om privé-implementatie in een header-eenheid te verbergen. Om aan te geven dat privé-implementatie met headerbestanden wordt gebruikt, worden verschillende technieken gebruikt, zoals het toevoegen van voorlooponderstrepingstekens aan namen of het plaatsen van dingen in een implementatienaamruimte. Een module biedt zijn private implementatie op geen enkele manier aan, dus u hoeft dat niet te doen.

Overweeg uw vooraf gecompileerde headers te vervangen door header-eenheden. U krijgt hetzelfde snelheidsvoordeel, maar ook met andere voordelen van code hygiëne en flexibiliteit.

Manieren om een header-eenheid te compileren

Er zijn verschillende manieren om een bestand te compileren in een header-eenheid:

  • Bouw een project voor een gedeelde headereenheid. We raden deze aanpak aan omdat deze meer controle biedt over de organisatie en het hergebruik van de geïmporteerde headereenheden. Maak een statisch bibliotheekproject met de gewenste headerbestanden en refereer eraan om de headerbestanden te importeren. Zie Een statische-bibliotheekproject voor kopteksteenheden bouwen voor een overzicht van deze benadering.

  • Kies afzonderlijke bestanden die u wilt vertalen in kopteksteenheden. Met deze methode kunt u bestand-per-bestand beheren wat wordt behandeld als een header-eenheid. Het is ook handig wanneer u een bestand moet compileren als een header-eenheid die, omdat het niet beschikt over de standaardextensie (.ixx, .cppm, .h, .hpp), normaal gesproken niet wordt gecompileerd in een header-eenheid. Deze aanpak wordt in deze handleiding gedemonstreerd. Om aan de slag te gaan, zie Benadering 1: Een specifiek bestand vertalen naar een header-unit.

  • Scan en bouw automatisch header-eenheden. Deze aanpak is handig, maar is meer geschikt voor kleinere projecten omdat het geen optimale bouwdoorvoer garandeert. Zie Benadering 2 voor meer informatie over deze aanpak : Automatisch scannen op kopteksteenheden.

  • Zoals vermeld in de inleiding, kunt u STL-headerbestanden bouwen en importeren als kopteksteenheden en automatisch behandelen #include voor STL-bibliotheekheaders, net zoals import zonder uw code opnieuw te schrijven. Als u wilt zien hoe, gaat u naar Walkthrough: STL-bibliotheken importeren als header-eenheden.

Benadering 1: Een specifiek bestand omzetten in een header-eenheid

In deze sectie leert u hoe u een specifiek bestand kiest om te vertalen naar een header-eenheid. Compileer een headerbestand als een header-eenheid met behulp van de volgende stappen in Visual Studio:

  1. Maak een nieuw C++-console-app-project.

  2. Vervang de inhoud van het bronbestand als volgt:

    #include "Pythagorean.h"
    
    int main()
    {
        PrintPythagoreanTriple(2,3);
        return 0;
    }
    
  3. Voeg een headerbestand toe met de naam Pythagorean.h en vervang de inhoud door deze code:

    #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
    

Projecteigenschappen instellen

Als u kopteksteenheden wilt inschakelen, stelt u eerst de C++-taalstandaard in op /std:c++20 of later met de volgende stappen:

  1. Klik in Solution Explorer met de rechtermuisknop op de projectnaam en kies Eigenschappen.
  2. Selecteer In het linkerdeelvenster van het venster met eigenschappenpagina's van het project de optie Configuratie-eigenschappen>algemeen.
  3. Selecteer in de vervolgkeuzelijst C++ Language StandardISO C++20 Standard (/std:c++20) of hoger. Kies OK om het dialoogvenster te sluiten.

Compileer het headerbestand als een header-eenheid:

  1. Selecteer in Solution Explorer het bestand dat u wilt compileren als een header-eenheid (in dit geval). Pythagorean.h Klik met de rechtermuisknop op het bestand en kies Eigenschappen.

  2. Stel de vervolgkeuzelijst Configuratie-eigenschappen>Algemeen>Itemtype in op C/C++-compiler en kies Ok.

    Schermopname van het wijzigen van het itemtype in C/C++-compiler.

Wanneer u dit project later in deze handleiding bouwt, zal Pythagorean.h worden omgezet in een header-eenheid. Het wordt omgezet in een kopteksteenheid omdat het itemtype voor dit headerbestand is ingesteld op de C/C++-compiler en omdat de standaardactie voor .h en .hpp bestanden op deze manier het bestand omzetten in een kopteksteenheid.

Opmerking

Dit is niet vereist voor deze handleiding, maar wordt verstrekt voor uw informatie. Als u een bestand wilt compileren als een header-eenheid die geen standaardbestandsextensie voor headereenheden heeft, stel dan .cpp>>> in op Compileren als C++ Header-eenheid (/exportHeader): Screenshot that shows changing Configuration properties  C/C++ > Advanced > Compile As to Compile as C++ Header Unit (/exportHeader).>

De code wijzigen om de header-eenheid te importeren

  1. Wijzig in het bronbestand voor het voorbeeldproject #include "Pythagorean.h" naar import "Pythagorean.h";. Vergeet de puntkomma aan het einde niet. Dit is vereist voor import instructies. Omdat het een headerbestand is in een map die lokaal is voor het project, hebben we aanhalingstekens gebruikt met de import instructie: import "file";. Gebruik in uw eigen projecten hoekhaken om een header-unit te compileren vanuit een systeem-header: import <file>;

  2. Bouw de oplossing door Bouwen>Oplossing bouwen te selecteren in het hoofdmenu. Voer deze uit om te zien dat de verwachte uitvoer wordt geproduceerd: Pythagorean triple a:2 b:3 c:13

Herhaal dit proces in uw eigen projecten om de headerbestanden te compileren die u als kopteksteenheden wilt importeren.

Als u slechts een paar headerbestanden wilt converteren naar header-eenheden, is deze methode goed. Maar als u veel headerbestanden hebt die u wilt compileren en het potentiële verlies van buildprestaties wordt weggewegen door het gemak van het automatisch verwerken van het buildsysteem, raadpleegt u de volgende sectie.

Als u specifiek STL-bibliotheekheaders als kopbestandseenheden wilt importeren, zie Walkthrough: STL-bibliotheken importeren als kopbestandseenheden.

Benadering 2: Automatisch scannen op en bouw header-eenheden

Omdat het tijd kost om al uw bronbestanden te scannen op headereenheden en tijd om ze te bouwen, is de volgende benadering het meest geschikt voor kleinere projecten. Het garandeert geen optimale build-verwerkingssnelheid.

Deze benadering combineert twee Visual Studio-projectinstellingen:

  • Het scannen van bronnen voor moduleafhankelijkheden zorgt ervoor dat het buildsysteem de compiler aanroept om ervoor te zorgen dat alle geïmporteerde modules en headerbestanden worden gebouwd voordat de bestanden worden gecompileerd die hiervan afhankelijk zijn. In combinatie met Translate Includes to Imports worden alle headerbestanden die in uw bron zijn opgenomen en die ook zijn opgegeven in een header-units.json bestand in dezelfde map als het headerbestand, gecompileerd tot header-eenheden.
  • Vertaal 'Includes' naar 'Imports' behandelt een headerbestand als een als het import verwijst naar een headerbestand dat kan worden gecompileerd als een header-eenheid (zoals gespecificeerd in een #include-bestand) en een gecompileerde header-eenheid beschikbaar is voor het headerbestand. Anders wordt het headerbestand als normaal #includebehandeld. Het header-units.json bestand wordt gebruikt om automatisch header-eenheden voor elk #includete bouwen, zonder symboolduplicatie.

U kunt deze instellingen inschakelen in de eigenschappen voor uw project. Hiervoor klikt u met de rechtermuisknop op het project in Solution Explorer en kiest u Eigenschappen. Kies vervolgens Configuratie-eigenschappen>C/C++>Algemeen.

Schermopname van het projecteigenschappenvenster met Configuratie gemarkeerd en Alle configuraties geselecteerd. Onder C/C++ > Algemeen is Bronnen scannen op moduleafhankelijkheden gemarkeerd en ingesteld op Ja, en Inclusies vertalen naar Imports is gemarkeerd en ingesteld op Ja (/translateInclude)

Scanbronnen voor moduleafhankelijkheden kunnen worden ingesteld voor alle bestanden in het project in Projecteigenschappen , zoals hier wordt weergegeven of voor afzonderlijke bestanden in bestandseigenschappen. Modules en header-eenheden worden altijd gescand. Stel deze optie in wanneer u een .cpp bestand hebt dat header-eenheden importeert die u automatisch wilt laten bouwen en mogelijk nog niet zijn gebouwd.

Deze instellingen werken samen om header-eenheden automatisch te bouwen en te importeren onder deze voorwaarden:

  • Bronnen scannen op moduleafhankelijkheden scant uw bronnen voor de bestanden en de bijbehorende afhankelijkheden die kunnen worden behandeld als header-eenheden. Bestanden met de extensie .ixxen bestanden waarvan de eigenschap>C/C++>Compile As is ingesteld op Compile as C++ Header Unit (/export), worden altijd gescand, ongeacht deze instelling. De compiler zoekt ook naar import instructies om afhankelijkheden van headereenheden te identificeren. Als /translateInclude is opgegeven, scant de compiler ook naar #include-instructies die eveneens zijn opgegeven in een header-units.json-bestand om als header-eenheden te behandelen. Een afhankelijkheidsgrafiek is opgebouwd van alle modules en header-eenheden in uw project.
  • Vertaal Inclusief naar Import Wanneer de compiler een #include instructie tegenkomt en er een overeenkomende headerbestandseenheid (.ifc) bestaat voor het opgegeven header bestand, importeert de compiler de header-eenheid in plaats van het header bestand te behandelen als een #include. In combinatie met Scannen op afhankelijkheden vindt de compiler alle headerbestanden die kunnen worden gecompileerd in headereenheden. Een allowlist wordt door de compiler geraadpleegd om te bepalen welke headerbestanden in header-eenheden kunnen worden gecompileerd. Deze lijst wordt opgeslagen in een header-units.json bestand dat zich in dezelfde map moet bevinden als het opgenomen bestand. U ziet een voorbeeld van een header-units.json bestand onder de installatiemap voor Visual Studio. Wordt bijvoorbeeld %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json door de compiler gebruikt om te bepalen of een standaardsjabloonbibliotheekheader kan worden gecompileerd in een header-eenheid. Deze functionaliteit bestaat om te fungeren als een brug met verouderde code om enkele voordelen van header-eenheden te krijgen.

Het header-units.json bestand heeft twee doeleinden. Naast het specificeren welke headerbestanden kunnen worden gecompileerd in kopbestanden, worden dubbele symbolen geminimaliseerd om de doorvoer van de build te verhogen. Zie C++ header-units.json naslaginformatie voor meer informatie over symboolduplicatie.

Deze schakelaars en de header-unit.json bieden een aantal van de voordelen van kopteksteenheden. Het gemak komt ten koste van de buildsnelheid. Deze benadering is mogelijk niet het beste voor grotere projecten, omdat dit geen optimale buildtijden garandeert. Ook kunnen dezelfde headerbestanden herhaaldelijk opnieuw worden verwerkt, waardoor de buildtijd toeneemt. Het gemak kan echter de moeite waard zijn, afhankelijk van het project.

Deze functies zijn ontworpen voor verouderde code. Voor nieuwe code, gebruik modules in plaats van header-eenheden of #include bestanden. Zie de zelfstudie Over naammodules (C++) voor een zelfstudie over het gebruik van modules.

Zie Walkthrough: STL-bibliotheken importeren als header units voor een voorbeeld van hoe deze techniek wordt gebruikt om STL-headerbestanden als header units te importeren.

Gevolgen van preprocessor

De standaard C99/C++11 die voldoet aan de preprocessor is vereist voor het maken en gebruiken van header-eenheden. De compiler maakt het mogelijk om de nieuwe C99/C++11-conforme preprocessor te gebruiken bij het compileren van header-eenheden door impliciet /Zc:preprocessor toe te voegen aan de opdrachtregel bij gebruik van een vorm van /exportHeader. Als u het probeert uit te schakelen, treedt er een compilatiefout op.

Het inschakelen van de nieuwe preprocessor is van invloed op de verwerking van variadic macro's. Zie de sectie Variadic macro's opmerkingen voor meer informatie.

Zie ook

/translateInclude
/exportHeader
/headerUnit
header-units.json
Header-eenheden, modules en voorgecompileerde headers vergelijken
Overzicht van modules in C++
Zelfstudie: De standaardbibliotheek van C++ importeren met behulp van modules
Handleiding: STL-bibliotheken importeren als header-eenheden