Sejak Chapter Kompilasi Program kita sudah sesekali menulis program multi-file, tetapi pembagiannya masih menumpang pada kebetulan. Pada Chapter ini kita formalisasi aturannya: kode C++ kita hidup di dua jenis file yang bekerja berpasangan — header (.h atau .hpp) yang berisi apa yang tersedia, dan source file (.cpp) yang berisi bagaimana itu diimplementasikan. Pembagian ini bukan sekadar kerapian: compiler memperlakukan keduanya secara berbeda, dan cara kita membagi kode menentukan seberapa cepat proyek dikompilasi ulang seiring tumbuhnya. Kita akan membangun satu modul matematika lengkap dari tiga file, mengompilasinya sungguhan, dan melihat error klasik yang muncul bila aturannya dilanggar.

Dua Jenis File yang Berpasangan #

Header adalah kartu nama modul: ia mendaftarkan deklarasi — daftar fungsi, class, dan konstanta yang bisa dipakai file lain — tanpa menuliskan isi implementasinya. Source file adalah dapurnya: di sinilah definisi sesungguhnya ditulis, lengkap dengan detail yang tidak perlu diketahui pemakai. File lain yang ingin memakai modul cukup meng-include header-nya, dan linker (pembahas akhir program yang kita kenal pada Chapter Proses Kompilasi dan Linking) nanti menyambungkan panggilan itu ke definisi di source file.

Perhatikan dua hal kecil tentang penamaan. Ekstensi .h dan .hpp sama-sama sah untuk header C++ — .h adalah konvensi warisan C yang paling umum, sedangkan .hpp menegaskan “ini header C++, bukan C”; course ini memakai .h. Kedua, header tidak pernah dikompilasi langsung: g++ utama.cpp matematika.cpp hanya menyebut file .cpp, karena header masuk ke dalam kompilasi lewat #include — isi header disalin verbatim ke setiap file yang memanggilnya.

Membangun Modul matematika #

Kita praktikkan pada modul tiga file berikut. Header-nya hanya berisi deklarasi, plus satu fungsi inline kecil yang kita bahas sesaat lagi:

cpp matematika.h — kartu nama modul
#include <vector>

// Deklarasi (prototype): implementasinya ada di matematika.cpp
int tambah(int a, int b);
int kali(int a, int b);
double rataRata(const std::vector<int>& nilai);

// Fungsi kecil satu baris: inline, boleh langsung didefinisikan di header
inline int kuadrat(int x) {
    return x * x;
}

Source file mendefinisikan ketiga fungsi yang dideklarasikan header. Perhatikan bahwa baris pertamanya justru meng-include header-nya sendiri — kebiasaan penting agar compiler memeriksa bahwa deklarasi dan definisi tidak pernah berbeda:

cpp matematika.cpp — dapur implementasi
#include "matematika.h"

int tambah(int a, int b) {
    return a + b;
}

int kali(int a, int b) {
    return a * b;
}

double rataRata(const std::vector<int>& nilai) {
    if (nilai.empty()) {
        return 0.0;
    }
    double total = 0.0;
    for (int n : nilai) {
        total += n;
    }
    return total / nilai.size();
}

Terakhir, pemakai modul: ia meng-include <iostream> karena mencetak, <vector> karena membuat vector sendiri, dan "matematika.h" karena memanggil fungsi modul:

cpp utama.cpp — pemakai modul
#include <iostream>
#include <vector>
#include "matematika.h"

int main() {
    std::cout << "8 + 5 = " << tambah(8, 5) << "\n";
    std::cout << "8 x 5 = " << kali(8, 5) << "\n";
    std::cout << "9 kuadrat = " << kuadrat(9) << "\n";

    std::vector<int> nilai {80, 75, 90};
    std::cout << "Rata-rata = " << rataRata(nilai) << "\n";
    return 0;
}

Kompilasi multi-file yang sudah kita kenal: kedua .cpp dicantumkan, header tidak. Program berikut sudah kita uji di lingkungan praktikum:

bash
g++ -std=c++23 -Wall -Wextra utama.cpp matematika.cpp -o utama
./utama

Output yang Dihasilkan:

text
8 + 5 = 13
8 x 5 = 40
9 kuadrat = 81
Rata-rata = 81.6667

Apa yang Boleh di Header, Apa yang Tidak #

Aturan garis besarnya: header boleh berisi deklarasi dan hal-hal yang wajib terlihat teksnya oleh setiap pemakai; header tidak boleh berisi definisi yang akan lahir lebih dari satu kali. Rinciannya:

KontenDi header?Alasan
Deklarasi fungsiYaKartu nama modul
Definisi fungsi biasaTidak — taruh di .cppBisa berakhir dua definisi (pelanggaran ODR)
Fungsi inlineYaSecara eksplisit dikecualikan dari aturan itu
Definisi class/structYaCompiler butuh ukuran dan layout-nya di setiap pemakai
TemplateYaDiinstansiasi per pemakai, kode harus terlihat
Definisi variabel const/constexprYaInternal linkage, aman diduplikasi per file
Definisi variabel biasaTidak — deklarasi extern sajaNanti dibahas pada Chapter Declaration vs Definition

Fungsi inline seperti kuadrat di atas adalah pintu resmi untuk menaruh definisi di header: keyword inline bukan perintah “percepat fungsi ini”, melainkan pemberitahuan “definisi ini boleh muncul berkali-kali di program, asal isinya sama” — tepat situasi yang terjadi ketika header di-include oleh banyak file. Template kita perlakukan sama pada Section Generic Programming dan Templates justru karena alasan itu: compiler harus melihat teks template saat menginstansiasinya, sehingga template praktis selalu hidup di header.

Include yang Minimal #

Ketika header matematika.h mendeklarasikan rataRata(const std::vector<int>&), header itu sendiri memakai std::vector — maka ia wajib meng-include <vector> sendiri, jangan berharap file pemakai kebetulan meng-include-nya lebih dulu. Prinsipnya sederhana: setiap file meng-include persis apa yang ia pakai, tidak lebih. Header yang boros include membuat semua pemakainya ikut membayar waktu kompilasi header-header itu; ingat, satu #include <iostream> bisa membengkakkan file menjadi puluhan ribu baris hasil preprocessor. Adapun file .cpp cukup meng-include <iostream>, <vector>, dan header modulnya — tidak ada pihak ketiga.

Apa yang terjadi bila aturan include dilanggar? Kita uji: utama.cpp versi yang lupa meng-include matematika.h:

cpp utama-tanpa-include.cpp — versi yang lupa include
#include <iostream>

int main() {
    std::cout << tambah(8, 5) << "\n";
    return 0;
}

Hasil kompilasinya (dari eksekusi nyata di lingkungan praktikum):

text
utama-tanpa-include.cpp: In function ‘int main()’:
utama-tanpa-include.cpp:4:18: error: ‘tambah’ was not declared in this scope
    4 |     std::cout << tambah(8, 5) << "\n";
      |                  ^~~~~~

Pesan ‘tambah’ was not declared in this scope adalah wajah dari masalah include yang hilang: compiler membaca utama.cpp dari atas ke bawah, dan pada baris 4 belum ada pendaftaran nama tambah. Ini salah satu error tersering pemula — dan sekarang kita tahu diagnosis pertamanya: periksa #include, baru curiga ke logika program.

Dengan pembagian header dan source yang rapi, satu pertanyaan baru muncul: ketika sebuah nama dideklarasikan di satu tempat dan didefinisikan di tempat lain, apa garis batas persisnya — dan apa yang terjadi bila garis batas itu dilanggar? Chapter berikutnya membedah pasangan declaration dan definition, variabel extern, serta aturan ODR yang menjadi fondasi seluruh mekanisme ini.

Referensi: #include di cppreference , Inline specification di cppreference .