Header kita sejauh ini bertahan tanpa insiden, tetapi itu karena kebetulan: belum ada satu pun header yang dimasukkan dua kali ke file yang sama. Pada proyek nyata kejadian itu biasa saja — utama.cpp meng-include dua header, dan ternyata keduanya meng-include header ketiga yang sama. Karena #include menyalin isi file verbatim, struct atau fungsi yang sama terdaftar dua kali, dan compiler menolak dengan error redefinition. Chapter ini membahas penyebabnya dan pola pelindung standar — include guards — yang wajib ada di setiap header yang Anda tulis mulai hari ini.
Jebakan: Header yang Masuk Dua Kali #
Kita buat jebakannya secara sengaja dan amati pesan aslinya. Tiga file, tanpa pelindung apa pun. Header kotak-salah.h mendefinisikan struct Kotak:
struct Kotak {
double sisi;
};
double volume(const Kotak& k);Header paket-salah.h memakai Kotak dan meng-include header tadi:
#include <string>
#include "kotak-salah.h"
struct Paket {
Kotak isi;
std::string label;
};
double beratTotal(const Paket& p);Dan utama-salah.cpp meng-include keduanya — pola yang sangat wajar, karena utama memang memakai Kotak langsung dan memakai Paket:
#include <iostream>
#include "kotak-salah.h"
#include "paket-salah.h"
int main() {
Kotak k {3.0};
std::cout << volume(k) << "\n";
return 0;
}Kompilasi utama-salah.cpp gagal dengan pesan nyata berikut dari lingkungan praktikum:
In file included from paket-salah.h:2,
from utama-salah.cpp:3:
kotak-salah.h:1:8: error: redefinition of ‘struct Kotak’
1 | struct Kotak {
| ^~~~~
In file included from utama-salah.cpp:2:
kotak-salah.h:1:8: note: previous definition of ‘struct Kotak’
1 | struct Kotak {
| ^~~~~Pesan itu mudah dibaca sekali kita pahami mekanismenya. Preprocessor menyalin kotak-salah.h pertama kali saat memproses baris 2 dari utama-salah.cpp — struct Kotak terdefinisi. Lalu saat memproses baris 3, paket-salah.h menarik kotak-salah.h yang sama untuk kedua kalinya, dan compiler menemukan definisi struct Kotak kedua — pelanggaran yang sudah kita kenal pada Chapter Declaration vs Definition, kali ini terjadi di satu file yang sama, sebelum urusan linker pun dimulai.
Pola Include Guard: ifndef, define, endif #
Pelahnya, solusinya sudah berumur puluhan tahun dan hanya butuh tiga baris preprocessor. Setiap header kita bungkus seperti ini — inilah versi aman dari modul tadi:
#ifndef KOTAK_H
#define KOTAK_H
struct Kotak {
double sisi;
};
double volume(const Kotak& k);
#endif#ifndef PAKET_H
#define PAKET_H
#include <string>
#include "kotak.h"
struct Paket {
Kotak isi;
std::string label;
};
double beratTotal(const Paket& p);
#endifMekanismenya berlapis tiga. Baris #ifndef KOTAK_H bertanya “apakah makro KOTAK_H belum didefinisikan?” — jika belum, seluruh isi header diproses dan baris #define KOTAK_H mendaftarkan makro itu. Ketika header yang sama masuk untuk kedua kalinya, KOTAK_H sudah terdaftar, #ifndef gagal, dan preprocessor melewati seluruh isi sampai #endif — definisi kedua tidak pernah sampai ke compiler. Guard bekerja pada level preprocessor, jadi biayanya nol saat program berjalan.
Dengan pelindung di kedua header, kompilasi tiga file lengkap berjalan mulus:
g++ -std=c++23 -Wall -Wextra utama.cpp kotak.cpp paket.cpp -o utama
./utamaOutput yang Dihasilkan:
Volume kotak : 27
Berat paket : 13.5 kg
Label : Buku Tugas#include "kotak.h"
double volume(const Kotak& k) {
return k.sisi * k.sisi * k.sisi;
}#include "paket.h"
double beratTotal(const Paket& p) {
return volume(p.isi) * 0.5; // berat = setengah volume (kg per m3)
}#include <iostream>
#include "kotak.h"
#include "paket.h"
int main() {
Kotak k {3.0};
Paket p {k, "Buku Tugas"};
std::cout << "Volume kotak : " << volume(k) << "\n";
std::cout << "Berat paket : " << beratTotal(p) << " kg\n";
std::cout << "Label : " << p.label << "\n";
return 0;
}Menamai Guard dengan Benar #
Kekuatan pola ini berdiri di satu premis: guard tiap header harus unik di seluruh proyek. Konvensi yang umum mengikuti posisi file — KOTAK_H untuk kotak.h, atau lebih panjang bila proyek punya banyak folder: SISWA_KELULUSAN_REPORT_H untuk include/kelulusan/report.h. Bila dua header tak sengaja memakai guard sama, header kedua akan dilewati diam-diam: kompilasi sukses, tetapi tipe yang Anda kira tersedia justru tidak pernah masuk — bug yang menyamar sebagai “compiler hilang ingatan”. Karena nama guard hanya dilihat oleh preprocessor, bentuknya bebas; yang penting konsisten dan mudah dikaitkan dengan filenya.
Dengan guard, header sudah aman untuk saling meng-include. Meski begitu, menulis tiga baris pelindung untuk setiap header tetap kerja manual — dan ada satu baris tunggal yang bisa menggantikannya. Chapter berikutnya membahas #pragma once: alternatif yang lebih pendek, kelebihan dan kekurangannya, serta posisinya di standar bahasa.
Referensi: Preprocessor di cppreference , Conditional inclusion di cppreference .