Headerdateien (C++)

Die Namen von Programmelementen wie Variablen, Funktionen, Klassen usw. müssen deklariert werden, bevor sie verwendet werden können. Sie können z. B. nicht nur schreiben x = 42 , ohne zuerst "x" zu deklarieren.

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

Die Deklaration teilt dem Compiler mit, ob es sich bei dem Element um eine int, eine doubleFunktion, eine class oder eine andere Sache handelt. Darüber hinaus muss jeder Name (direkt oder indirekt) in jeder CPP-Datei deklariert werden, in der er verwendet wird. Wenn Sie ein Programm kompilieren, wird jede CPP-Datei unabhängig in einer Kompilierungseinheit kompiliert. Der Compiler hat keine Kenntnisse darüber, welche Namen in anderen Kompilierungseinheiten deklariert werden. Das bedeutet: Wenn Sie eine Klasse oder Funktion oder globale Variable definieren, müssen Sie in jeder zusätzlichen CPP-Datei, die sie verwendet, eine Deklaration dieser Datei angeben. Jede Deklaration dieser Sache muss in allen Dateien exakt identisch sein. Eine leichte Inkonsistenz verursacht Fehler oder unbeabsichtigtes Verhalten, wenn der Linker versucht, alle Kompilierungseinheiten in einem einzigen Programm zusammenzuführen.

Um das Fehlerpotenzial zu minimieren, hat C++ die Konvention der Verwendung von Headerdateien für Deklarationen übernommen. Sie erstellen die Deklarationen in einer Headerdatei und verwenden dann die #include-Direktive in jeder CPP-Datei oder einer anderen Headerdatei, die diese Deklaration erfordert. Die #include Direktive fügt vor der Kompilierung eine Kopie der Headerdatei direkt in die CPP-Datei ein.

Hinweis

In Visual Studio 2019 wird das C++20-Modulfeature als Verbesserung und letztendlicher Ersatz für Headerdateien eingeführt. Weitere Informationen finden Sie unter Übersicht über Module in C++.

Beispiel

Das folgende Beispiel zeigt eine gängige Methode zum Deklarieren einer Klasse und anschließender Verwendung in einer anderen Quelldatei. Wir beginnen mit der Headerdatei. my_class.h Sie enthält eine Klassendefinition, beachten Sie jedoch, dass die Definition unvollständig ist; die Memberfunktion do_something ist nicht definiert:

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

}

Erstellen Sie als Nächstes eine Implementierungsdatei (in der Regel mit einer CPP- oder ähnlichen Erweiterung). Wir rufen die Datei my_class.cpp auf und stellen eine Definition für die Memberdeklaration bereit. Wir fügen eine #include Direktive für die Datei "my_class.h" hinzu, damit die my_class-Deklaration an diesem Punkt in die CPP-Datei eingefügt wird, und wir fügen <iostream> die Deklaration std::coutfür . Beachten Sie, dass Anführungszeichen für Kopfzeilendateien im selben Verzeichnis wie die Quelldatei verwendet werden, und winkelige Klammern werden für Standardbibliotheksheader verwendet. Außerdem verfügen viele Standardbibliotheksheader nicht über .h oder eine andere Dateierweiterung.

In der Implementierungsdatei können wir optional eine using Anweisung verwenden, um zu vermeiden, dass alle Erwähnung von "my_class" oder "Cout" mit "N::" oder "std::"" qualifiziert werden müssen. Fügen using Sie keine Anweisungen in Ihre Kopfzeilendateien ein!

// 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;
}

Jetzt können wir in einer anderen CPP-Datei verwenden my_class . Wir #include die Headerdatei, sodass der Compiler die Deklaration abruft. Der gesamte Compiler muss wissen, dass my_class eine Klasse ist, die eine öffentliche Memberfunktion aufgerufen do_something()hat.

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

using namespace N;

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

Nachdem der Compiler die Kompilierung jeder CPP-Datei in OBJ-Dateien abgeschlossen hat, werden die OBJ-Dateien an den Linker übergeben. Wenn der Linker die Objektdateien zusammenführt, findet er genau eine Definition für my_class; sie befindet sich in der OBJ-Datei, die für my_class.cpp erstellt wurde, und der Build erfolgreich ist.

Schutzvorrichtungen einschließen

In der Regel verfügen Headerdateien über einen Include Guard oder eine #pragma once Direktive, um sicherzustellen, dass sie nicht mehrmals in eine einzelne CPP-Datei eingefügt werden.

// 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 */

Was in eine Headerdatei abgelegt werden soll

Da eine Headerdatei möglicherweise von mehreren Dateien eingeschlossen werden kann, kann sie keine Definitionen enthalten, die mehrere Definitionen desselben Namens erzeugen können. Folgendes ist nicht zulässig oder gilt als sehr schlechte Praxis:

  • Integrierte Typdefinitionen im Namespace- oder globalen Bereich
  • Nicht-Inline-Funktionsdefinitionen
  • Nichtkonstvariablendefinitionen
  • Aggregatdefinitionen
  • Unbenannte Namespaces
  • using-Direktiven

Die Verwendung der using Direktive führt nicht unbedingt zu einem Fehler, kann aber möglicherweise zu einem Problem führen, da er den Namespace in jede CPP-Datei einfügt, die direkt oder indirekt diesen Header enthält.

Beispielheaderdatei

Das folgende Beispiel zeigt die verschiedenen Arten von Deklarationen und Definitionen, die in einer Headerdatei zulässig sind:

// 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;
}