Koptekstbestanden (C++)

De namen van programma-elementen, zoals variabelen, functies, klassen, enzovoort, moeten worden gedeclareerd voordat ze kunnen worden gebruikt. U kunt bijvoorbeeld niet alleen schrijven x = 42 zonder eerst 'x' te declareren.

int x; // declaration
x = 42; // use x

De declaratie vertelt de compiler of het element een int, een double, een functie of class een ander element is. Bovendien moet elke naam worden gedeclareerd (direct of indirect) in elk .cpp bestand waarin deze wordt gebruikt. Wanneer u een programma compileert, wordt elk .cpp bestand onafhankelijk gecompileerd in een compilatie-eenheid. De compiler weet niet welke namen worden gedeclareerd in andere compilatie-eenheden. Dat betekent dat als u een klasse of functie of globale variabele definieert, u een declaratie van dat ding moet opgeven in elk extra .cpp bestand dat het gebruikt. Elke verklaring van dat ding moet exact identiek zijn in alle bestanden. Een lichte inconsistentie veroorzaakt fouten of onbedoeld gedrag wanneer de linker probeert alle compilatie-eenheden samen te voegen in één programma.

Om het potentieel van fouten te minimaliseren, heeft C++ de conventie aangenomen voor het gebruik van headerbestanden om declaraties te bevatten. U maakt de declaraties in een headerbestand en gebruikt vervolgens de #include instructie in elk .cpp bestand of ander headerbestand waarvoor die declaratie is vereist. Met de #include-instructie wordt een kopie van het headerbestand rechtstreeks in het .cpp bestand ingevoegd voordat het wordt gecompileert.

Note

In Visual Studio 2019 wordt de C++20-modulesfunctie geïntroduceerd als verbetering en uiteindelijke vervanging voor headerbestanden. Zie Overzicht van modules in C++voor meer informatie.

Example

In het volgende voorbeeld ziet u een veelvoorkomende manier om een klasse te declareren en deze vervolgens in een ander bronbestand te gebruiken. We beginnen met het headerbestand. my_class.h Het bevat een klassedefinitie, maar houd er rekening mee dat de definitie onvolledig is; de lidfunctie do_something is niet gedefinieerd:

// my_class.h
namespace N
{
    class my_class
    {
    public:
        void do_something();
    };

}

Maak vervolgens een implementatiebestand (meestal met een .cpp of een vergelijkbare extensie). We noemen het bestand my_class.cpp en geven een definitie op voor de liddeclaratie. We voegen een #include richtlijn toe voor het bestand 'my_class.h' om de my_class declaratie op dit punt in het .cpp bestand in te voegen, en we voegen <iostream> toe om de declaratie voor std::coutin te trekken. Houd er rekening mee dat aanhalingstekens worden gebruikt voor koptekstbestanden in dezelfde map als het bronbestand en dat punthaken worden gebruikt voor standaardbibliotheekheaders. Bovendien hebben veel standaardbibliotheekheaders geen .h of een andere bestandsextensie.

In het implementatiebestand kunnen we eventueel een using instructie gebruiken om te voorkomen dat elke vermelding van 'my_class' of 'cout' met 'N::' of 'std::' moet worden gekwalificeerd. Plaats geen using instructies in uw koptekstbestanden.

// my_class.cpp
#include "my_class.h" // header in local directory
#include <iostream> // header in standard library

using namespace N;
using namespace std;

void my_class::do_something()
{
    cout << "Doing something!" << endl;
}

Nu kunnen we in een ander .cpp-bestand gebruiken my_class . We #include het headerbestand zodat de compiler de declaratie ophaalt. Het enige wat de compiler moet weten, is dat my_class een klasse is die een openbare lidfunctie heeft.do_something()

// my_program.cpp
#include "my_class.h"

using namespace N;

int main()
{
    my_class mc;
    mc.do_something();
    return 0;
}

Nadat de compiler klaar is met het compileren van elk .cpp bestand in .obj bestanden, worden de .obj bestanden doorgegeven aan de linker. Wanneer de linker de objectbestanden samenvoegt, vindt deze precies één definitie voor my_class; het is in het .obj bestand geproduceerd voor my_class.cpp en de build slaagt.

Bewakers opnemen

Header-bestanden hebben doorgaans een insluitingsbeveiliging of een #pragma once instructie om ervoor te zorgen dat ze niet meerdere keren worden ingevoegd in één .cpp bestand.

// my_class.h
#ifndef MY_CLASS_H // include guard
#define MY_CLASS_H

namespace N
{
    class my_class
    {
    public:
        void do_something();
    };
}

#endif /* MY_CLASS_H */

Wat u moet voorkomen in een koptekstbestand

Omdat een headerbestand mogelijk door meerdere bestanden kan worden opgenomen, kan het geen definities bevatten die meerdere definities van dezelfde naam kunnen produceren. Het volgende is niet toegestaan of wordt beschouwd als zeer slechte praktijken:

  • ingebouwde typedefinities op naamruimte of globaal bereik
  • niet-inline functiedefinities
  • niet-const-variabeledefinities
  • statistische definities
  • naamruimten zonder naam
  • met behulp van instructies

Het gebruik van de using richtlijn veroorzaakt niet noodzakelijkerwijs een fout, maar kan mogelijk een probleem veroorzaken omdat de naamruimte binnen het bereik komt in elk .cpp bestand dat direct of indirect die header bevat.

Voorbeeldheaderbestand

In het volgende voorbeeld ziet u de verschillende soorten declaraties en definities die zijn toegestaan in een headerbestand:

// sample.h
#pragma once
#include <vector> // #include directive
#include <string>

namespace N  // namespace declaration
{
    inline namespace P
    {
        //...
    }

    enum class colors : short { red, blue, purple, azure };

    const double PI = 3.14;  // const and constexpr definitions
    constexpr int MeaningOfLife{ 42 };
    constexpr int get_meaning()
    {
        static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert
        return MeaningOfLife;
    }
    using vstr = std::vector<int>;  // type alias
    extern double d; // extern variable

#define LOG   // macro definition

#ifdef LOG   // conditional compilation directive
    void print_to_log();
#endif

    class my_class   // regular class definition,
    {                // but no non-inline function definitions

        friend class other_class;
    public:
        void do_something();   // definition in my_class.cpp
        inline void put_value(int i) { vals.push_back(i); } // inline OK

    private:
        vstr vals;
        int i;
    };

    struct RGB
    {
        short r{ 0 };  // member initialization
        short g{ 0 };
        short b{ 0 };
    };

    template <typename T>  // template definition
    class value_store
    {
    public:
        value_store<T>() = default;
        void write_value(T val)
        {
            //... function definition OK in template
        }
    private:
        std::vector<T> vals;
    };

    template <typename T>  // template declaration
    class value_widget;
}