Condividi tramite


File di intestazione (C++)

I nomi degli elementi del programma, ad esempio variabili, funzioni, classi e così via, devono essere dichiarati prima di poter essere usati. Ad esempio, non è possibile scrivere x = 42 solo senza prima dichiarare "x".

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

La dichiarazione indica al compilatore se l'elemento è , intuna doublefunzione , una o un'altra class cosa. Inoltre, ogni nome deve essere dichiarato (direttamente o indirettamente) in ogni .cpp file in cui viene usato. Quando si compila un programma, ogni .cpp file viene compilato in modo indipendente in un'unità di compilazione. Il compilatore non conosce i nomi dichiarati in altre unità di compilazione. Ciò significa che se si definisce una classe o una funzione o una variabile globale, è necessario fornire una dichiarazione di tale elemento in ogni .cpp file aggiuntivo che lo usa. Ogni dichiarazione di questa cosa deve essere esattamente identica in tutti i file. Una leggera incoerenza causerà errori o comportamenti imprevisti quando il linker tenta di unire tutte le unità di compilazione in un singolo programma.

Per ridurre al minimo il rischio di errori, C++ ha adottato la convenzione di usare i file di intestazione per contenere dichiarazioni. Le dichiarazioni in un file di intestazione vengono quindi utilizzate la direttiva #include in ogni file di .cpp o in un altro file di intestazione che richiede tale dichiarazione. La direttiva #include inserisce una copia del file di intestazione direttamente nel file .cpp prima della compilazione.

Nota

In Visual Studio 2019, la funzionalità moduli C++20 viene introdotta come miglioramento e sostituzione finale per i file di intestazione. Per altre informazioni, vedere Panoramica dei moduli in C++.

Esempio

L'esempio seguente illustra un modo comune per dichiarare una classe e quindi usarla in un file di origine diverso. Si inizierà con il file di intestazione , my_class.h. Contiene una definizione di classe, ma si noti che la definizione è incompleta; la funzione do_something membro non è definita:

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

}

Creare quindi un file di implementazione (in genere con un .cpp o un'estensione simile). Il file verrà chiamato my_class.cpp e verrà specificata una definizione per la dichiarazione del membro. È stata aggiunta una #include direttiva per il file "my_class.h" per fare in modo che la dichiarazione di my_class sia inserita in questo punto nel file .cpp e che sia inclusa <iostream> per eseguire il pull nella dichiarazione per std::cout. Si noti che le virgolette vengono usate per i file di intestazione nella stessa directory del file di origine e le parentesi angolari vengono usate per le intestazioni della libreria standard. Inoltre, molte intestazioni della libreria standard non hanno estensione h o altre estensioni di file.

Nel file di implementazione è possibile usare facoltativamente un'istruzione using per evitare di dover qualificare ogni menzione di "my_class" o "cout" con "N::" o "std::". Non inserire using istruzioni nei file di intestazione.

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

È ora possibile usare my_class in un altro file di .cpp. È #include il file di intestazione in modo che il compilatore esestra la dichiarazione. Tutto il compilatore deve sapere che my_class è una classe con una funzione membro pubblica denominata do_something().

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

using namespace N;

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

Al termine della compilazione di ogni file .cpp in file .obj, il compilatore passa i file .obj al linker. Quando il linker unisce i file oggetto trova esattamente una definizione per my_class; si trova nel file .obj prodotto per my_class.cpp e la compilazione ha esito positivo.

Includi guardie

In genere, i file di intestazione hanno una protezione di inclusione o una #pragma once direttiva per assicurarsi che non vengano inseriti più volte in un singolo file .cpp.

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

Cosa inserire in un file di intestazione

Poiché un file di intestazione potrebbe essere potenzialmente incluso da più file, non può contenere definizioni che potrebbero produrre più definizioni con lo stesso nome. Di seguito non sono consentiti o sono considerati molto cattive pratiche:

  • Definizioni dei tipi predefinite nello spazio dei nomi o nell'ambito globale
  • Definizioni di funzioni non inline
  • definizioni di variabili non const
  • definizioni di aggregazione
  • spazi dei nomi senza nome
  • Direttive using

L'uso della using direttiva non causerà necessariamente un errore, ma può potenzialmente causare un problema perché porta lo spazio dei nomi nell'ambito in ogni file .cpp che include direttamente o indirettamente tale intestazione.

File di intestazione di esempio

L'esempio seguente illustra i vari tipi di dichiarazioni e definizioni consentiti in un file di intestazione:

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