File header (C++)

Nama elemen program seperti variabel, fungsi, kelas, dan sebagainya harus dideklarasikan sebelum dapat digunakan. Misalnya, Anda tidak dapat hanya menulis x = 42 tanpa terlebih dahulu mendeklarasikan 'x'.

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

Deklarasi memberi tahu pengkompilasi apakah elemen adalah int, , doublefungsi, class atau beberapa hal lainnya. Selain itu, setiap nama harus dideklarasikan (secara langsung atau tidak langsung) dalam setiap file .cpp tempat nama digunakan. Saat Anda mengkompilasi program, setiap file .cpp dikompilasi secara independen ke dalam unit kompilasi. Kompilator tidak memiliki pengetahuan tentang nama apa yang dideklarasikan dalam unit kompilasi lainnya. Itu berarti bahwa jika Anda menentukan kelas atau fungsi atau variabel global, Anda harus memberikan deklarasi hal itu di setiap file .cpp tambahan yang menggunakannya. Setiap deklarasi hal itu harus persis identik dalam semua file. Sedikit ketidakkonsistensian akan menyebabkan kesalahan, atau perilaku yang tidak diinginkan, ketika linker mencoba menggabungkan semua unit kompilasi ke dalam satu program.

Untuk meminimalkan potensi kesalahan, C++ telah mengadopsi konvensi penggunaan file header untuk berisi deklarasi. Anda membuat deklarasi dalam file header, lalu menggunakan arahan #include di setiap file .cpp atau file header lain yang memerlukan deklarasi tersebut. Direktif #include menyisipkan salinan file header langsung ke file .cpp sebelum kompilasi.

Catatan

Di Visual Studio 2019, fitur modul C++20 diperkenalkan sebagai peningkatan dan penggantian akhir untuk file header. Untuk informasi selengkapnya, lihat Gambaran umum modul di C++.

Contoh

Contoh berikut menunjukkan cara umum untuk mendeklarasikan kelas lalu menggunakannya dalam file sumber yang berbeda. Kita akan mulai dengan file header, my_class.h. Ini berisi definisi kelas, tetapi perhatikan bahwa definisi tidak lengkap; fungsi do_something anggota tidak ditentukan:

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

}

Selanjutnya, buat file implementasi (biasanya dengan ekstensi .cpp atau serupa). Kami akan memanggil file my_class.cpp dan memberikan definisi untuk deklarasi anggota. Kami menambahkan direktif #include untuk file "my_class.h" agar deklarasi my_class dimasukkan pada titik ini dalam file .cpp, dan kami menyertakan <iostream> untuk menarik dalam deklarasi untuk std::cout. Perhatikan bahwa tanda kutip digunakan untuk file header di direktori yang sama dengan file sumber, dan tanda kurung sudut digunakan untuk header pustaka standar. Selain itu, banyak header pustaka standar tidak memiliki .h atau ekstensi file lainnya.

Dalam file implementasi, kita dapat secara opsional menggunakan using pernyataan untuk menghindari harus memenuhi syarat setiap penyebutan "my_class" atau "cout" dengan "N::" atau "std::". Jangan letakkan using pernyataan dalam file header Anda!

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

Sekarang kita dapat menggunakan my_class dalam file .cpp lain. Kami #include file header sehingga pengkompilasi menarik dalam deklarasi. Semua pengkompilasi perlu tahu adalah bahwa my_class adalah kelas yang memiliki fungsi anggota publik yang disebut do_something().

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

using namespace N;

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

Setelah pengkompilasi selesai mengkompilasi setiap file .cpp ke dalam file .obj, ia meneruskan file .obj ke linker. Ketika linker menggabungkan file objek, ia menemukan tepat satu definisi untuk my_class; itu ada dalam file .obj yang diproduksi untuk my_class.cpp, dan build berhasil.

Sertakan penjaga

Biasanya, file header memiliki penjaga sertakan atau #pragma once arahan untuk memastikan bahwa file tersebut tidak dimasukkan beberapa kali ke dalam satu 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 */

Apa yang harus dihindari dalam file header

Karena file header mungkin berpotensi disertakan oleh beberapa file, file tidak dapat berisi definisi yang mungkin menghasilkan beberapa definisi dengan nama yang sama. Berikut ini tidak diizinkan, atau dianggap sebagai praktik yang sangat buruk:

  • definisi jenis bawaan di namespace layanan atau cakupan global
  • definisi fungsi non-sebaris
  • definisi variabel non-const
  • definisi agregat
  • namespace yang tidak disebutkan namanya
  • menggunakan direktif

Penggunaan direktif using tidak akan selalu menyebabkan kesalahan, tetapi berpotensi menyebabkan masalah karena membawa namespace ke dalam cakupan dalam setiap file .cpp yang secara langsung atau tidak langsung menyertakan header tersebut.

Contoh file header

Contoh berikut menunjukkan berbagai jenis deklarasi dan definisi yang diizinkan dalam file header:

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