Preprocessor, Header Files ve Forward Declarations
Bir C++ programı derlenirken, aslında kodun önce bir "ön işlemciden" (preprocessor) geçtiğini biliyor muydun? Derleyici senin yazdığın kodu görmeden önce, preprocessor direktifleri çoktan işlenmiş ve kod başka bir hale dönüşmüş oluyor. Bu ders, tam da bu sahne arkasını anlatıyor.
Header dosyaları, guard mekanizmaları, forward declaration'lar — bunlar büyük projelerde hayatta kalmanın temel taşları. Küçük bir "hello world" programında fark etmezsin, ama 50 dosyalı bir projede bunları bilmemek kabus demek.
Preprocessor Nedir?
Preprocessor, derleyiciden (compiler) önce çalışan bir metin işleme aracıdır. Kodunu okur, # ile başlayan direktifleri işler ve derleyiciye "temizlenmiş" bir çıktı verir. Derleyici aslında senin yazdığın .cpp dosyasını değil, preprocessor'ın ürettiği çıktıyı derler.
Bunu şöyle düşün: bir restoranda yemek siparişi veriyorsun. Garson (preprocessor) siparişini alıp mutfağa (compiler) iletmeden önce bazı şeyler yapıyor — "her zamanki" dediğinde bunu açık bir siparişe çeviriyor, menüde olmayan bir şey istediysen seni uyarıyor. Mutfak senin ne söylediğini bilmez, garsonun ilettiğini bilir.
Preprocessor'ın Temel İşleri
Dosya dahil etme (
#include)Makro tanımlama ve genişletme (
#define)Koşullu derleme (
#ifdef,#ifndef,#if,#else,#endif)Pragma direktifleri (
#pragma)
Her biri # karakteriyle başlar ve satır sonunda biter. Bu direktifler C++ dilinin parçası değildir — preprocessor'ın kendi dilidir.
#include Direktifi
#include en sık kullanılan preprocessor direktifidir. Başka bir dosyanın içeriğini, o satıra "yapıştırır." Gerçekten de bu kadar basit — kopyala-yapıştır gibi düşünebilirsin.
// Bu satır, iostream dosyasının TÜम içeriğini buraya kopyalar
#include <iostream>
// Bu satır, kendi header dosyanın içeriğini buraya kopyalar
#include "player.h"
int main() {
std::cout << "Merhaba!" << std::endl;
return 0;
}İki farklı #include sözdizimi var:
`<dosya>` — Sistem ve standart kütüphane header'larını dahil eder. Derleyici bunları "sistem include dizinlerinde" arar.
`"dosya"` — Önce projenin kendi dizininde arar, bulamazsa sistem dizinlerine bakar. Kendi yazdığın header'lar için bunu kullan.
#include <iostream> // Sistem — standart kütüphane
#include <vector> // Sistem — STL container
#include <SFML/Window.hpp> // Üçüncü parti kütüphane
#include "game.h" // Projenin kendi header'ı
#include "utils/math.h" // Alt dizindeki header💡 İpucu: Include sırası önemlidir! Genel kabul görmüş sıra: sistem → üçüncü parti → proje header'ları. Bu sayede bağımlılık sorunlarını erken fark edersin. Google C++ Style Guide de bu sırayı önerir.
#define ve Makrolar
#define direktifi, bir isim tanımlayıp ona bir değer (veya davranış) atamana olanak sağlar. Preprocessor, kodda o ismi her gördüğünde tanımladığın değerle değiştirir.
Basit Sabit Makrolar
#define PI 3.14159
#define MAX_PLAYERS 100
#define GAME_TITLE "Space Invaders"
double cevre = 2 * PI * yaricap; // Preprocessor bunu 2 * 3.14159 * yaricap yaparBu kullanım eskiden çok yaygındı ama artık önerilmiyor. Neden? Çünkü makrolar tip güvenliği (type safety) sağlamaz. PI derleyici için bir "double" değil, sadece bir metin parçasıdır.
Fonksiyon Benzeri Makrolar
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int sonuc = SQUARE(5); // ((5) * (5)) = 25
int buyuk = MAX(10, 20); // ((10) > (20) ? (10) : (20)) = 20Parantezlere dikkat! Onlar olmadan korkunç hatalar oluşabilir:
// KÖTÜ tanım — parantez eksik
#define SQUARE_BAD(x) x * x
int sonuc = SQUARE_BAD(3 + 1);
// Beklenen: (3+1)*(3+1) = 16
// Gerçek: 3 + 1 * 3 + 1 = 7 ← YANLIŞ!⚠️ Dikkat: Fonksiyon benzeri makrolar birçok tuzak barındırır. Yan etkileri olan ifadelerle kullanıldığında (mesela
SQUARE(i++)) beklenmedik sonuçlar verir, çünküi++iki kez değerlendirilir. Modern C++'ta bunlar yerineinlinefonksiyon veyaconstexprkullan.
Modern Alternatifler
C++11 ve sonrasında makroların yerini alan çok daha güvenli araçlar var:
// Makro yerine constexpr (derleme zamanı sabiti)
constexpr double PI = 3.14159265358979;
constexpr int MAX_PLAYERS = 100;
// Fonksiyon makrosu yerine inline veya constexpr fonksiyon
constexpr int square(int x) {
return x * x;
}
inline int maxVal(int a, int b) {
return (a > b) ? a : b;
}
// Template ile generic versiyon
template <typename T>
constexpr T square(T x) {
return x * x;
}constexpr değişkenler tip güvenlidir, debugger'da görünür ve scope kurallarına uyar. Makrolar ise global metin değiştirme yapar — kapsam (scope) kavramını bilmezler.
Makroların Hâlâ Kullanıldığı Yerler
Makrolar tamamen ölmedi. Bazı yerlerde hâlâ en iyi (veya tek) seçenekler:
// Koşullu derleme — platform kontrolü
#ifdef _WIN32
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#endif
// Header guards (birazdan göreceğiz)
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ...
#endif
// Debug makroları
#ifdef DEBUG
#define LOG(msg) std::cerr << "[DEBUG] " << msg << std::endl
#else
#define LOG(msg) // Boş — release'de hiçbir şey yapmaz
#endifKoşullu derleme, platforma özgü kod yazmanın en yaygın yoludur. Bu iş için makroların yerine geçen bir şey henüz yok (C++20 modules bu konuda ilerleme kaydetse de).
Header Dosyaları: .h vs .hpp
Header dosyaları, bildirim (declaration) içeren dosyalardır. Asıl amaçları, bir dosyadaki tanımların diğer dosyalar tarafından bilinmesini sağlamaktır.
Neden Header ve Source Ayrılır?
Bir sınıf yazdığını düşün. Eğer her şeyi tek bir dosyaya koyarsan, o sınıfı kullanan her dosya tüm implementasyonu da görmek zorunda kalır. Bu hem derleme süresini artırır hem de gereksiz bağımlılık yaratır.
Header dosyası (.h / .hpp): Bildirimleri tutar — "ne var" sorusuna cevap verir. Source dosyası (.cpp): Tanımları tutar — "nasıl çalışır" sorusuna cevap verir.
// player.h — BİLDİRİM (declaration)
#pragma once
#include <string>
class Player {
public:
Player(const std::string& name, int health);
void takeDamage(int amount);
bool isAlive() const;
std::string getName() const;
private:
std::string name_;
int health_;
};// player.cpp — TANIM (definition)
#include "player.h"
Player::Player(const std::string& name, int health)
: name_(name), health_(health) {}
void Player::takeDamage(int amount) {
health_ -= amount;
if (health_ < 0) health_ = 0;
}
bool Player::isAlive() const {
return health_ > 0;
}
std::string Player::getName() const {
return name_;
}.h mi .hpp mi?
Bu tamamen bir konvansiyon meselesidir — derleyici ikisi arasında fark görmez.
| Uzantı | Genel Kullanım |
|---|---|
.h | C ve C++ karışık projelerde, C uyumlu header'larda |
.hpp | Sadece C++ projelerde, C++ özelliklerini içeren header'larda |
.hxx, .hh | Bazı projelerin tercihi (daha az yaygın) |
Pratikte en yaygın iki yaklaşım: ya her şeye .h dersin (Boost, Google tarzı) ya da C++ header'larına .hpp dersin (Qt dışındaki birçok modern proje). Önemli olan tutarlılık — bir projede karıştırma.
Header Guards
Bir header dosyasının aynı derleme biriminde (translation unit) birden fazla kez dahil edilmesi (include) ciddi sorunlara yol açar. Aynı sınıf, fonksiyon veya değişken birden fazla kez tanımlanmış olur ve derleyici hata verir.
Bu nasıl olur? Şöyle düşün:
main.cpp → #include "a.h"
→ #include "b.h"
b.h → #include "a.h" ← a.h İKİNCİ KEZ dahil ediliyor!main.cpp derlenirken, a.h iki kez "yapıştırılmış" olur. İçindeki sınıf tanımı iki kez görülür → hata!
#ifndef / #define / #endif Pattern
Klasik çözüm, her header dosyasına bir "koruma" (guard) eklemektir:
// player.h
#ifndef PLAYER_H // "Eğer PLAYER_H daha önce tanımlanMADIysa..."
#define PLAYER_H // "PLAYER_H'ı tanımla"
#include <string>
class Player {
public:
Player(const std::string& name);
std::string getName() const;
private:
std::string name_;
};
#endif // PLAYER_H // "Koşullu bloğu kapat"İlk dahil edildiğinde PLAYER_H tanımlı değildir, bu yüzden #ifndef doğrudur (true) ve içerik işlenir. #define PLAYER_H satırı PLAYER_H'ı tanımlar. İkinci kez dahil edilmeye çalışıldığında PLAYER_H artık tanımlıdır, #ifndef yanlış (false) olur ve içerik atlanır.
Guard ismi genellikle dosya yoluyla eşleştirilir:
player.h→PLAYER_Hutils/math_helpers.h→UTILS_MATH_HELPERS_Hengine/render/shader.hpp→ENGINE_RENDER_SHADER_HPP
#pragma once (Modern Tercih)
#pragma once, aynı işi tek satırda yapar:
// player.h
#pragma once
#include <string>
class Player {
public:
Player(const std::string& name);
std::string getName() const;
private:
std::string name_;
};Avantajları:
Daha kısa ve okunaklı
İsim çakışması riski yok (guard ismi yanlış yazma gibi)
Derleyici optimizasyonu yapabilir (dosyayı tekrar açmayı bile deneyemeyebilir)
Dezavantajı:
Resmi C++ standardının parçası değil (ama GCC, Clang, MSVC hepsi destekler)
Aynı dosyanın farklı yollarla (symlink vs.) erişildiği durumlarda sorun çıkabilir (çok nadir)
Pratikte #pragma once modern projelerde fiilen standart haline geldi. Yine de bazı büyük projeler (Linux kernel gibi) hâlâ #ifndef guard kullanır. İkisini birlikte kullanmak da yaygın bir alışkanlıktır:
#pragma once
#ifndef PLAYER_H
#define PLAYER_H
// ... içerik ...
#endif💡 İpucu: Yeni başlıyorsan
#pragma oncekullan, kafanı gereksiz guard isimlendirme kurallarıyla yorma. Eğer çalıştığın proje#ifndefkullanıyorsa, projenin kuralına uy.
Forward Declaration
Bir sınıfı veya fonksiyonu kullanmak için mutlaka header dosyasını dahil etmen gerekmez. Eğer sadece "bu isimde bir şey var" demek yeterliyse, forward declaration kullanabilirsin.
Neden Forward Declaration?
İki temel neden var:
Derleme süresini azaltmak: Her
#include, preprocessor'ın o dosyayı açıp okumasını gerektirir. Bir header, başka header'ları dahil ediyorsa, zincirleme bir etki oluşur. Forward declaration bu zinciri kırar.Döngüsel bağımlılığı (circular dependency) çözmek: İki sınıf birbirine referans veriyorsa, ikisinin header'ı da birbirini include edemez. Forward declaration bu kilitlenmeyi açar.
Nasıl Kullanılır?
// YERİNE: #include "engine.h"
class Engine; // Forward declaration — "Engine diye bir sınıf var" diyoruz
class Car {
public:
Car(Engine* engine); // Pointer veya referans — OK
void start();
private:
Engine* engine_; // Pointer — OK
};Forward declaration yaptığında, o türün sadece adını biliyorsun. Boyutunu, metotlarını, üyelerini bilmiyorsun. Bu yüzden:
| Kullanım | Forward Declaration Yeterli mi? |
|---|---|
Pointer (Engine*) | ✅ Evet |
Referans (Engine&) | ✅ Evet |
| Fonksiyon parametresi/dönüş tipi | ✅ Evet |
Nesne oluşturma (Engine e;) | ❌ Hayır — boyut bilinmeli |
Metot çağrısı (e.start()) | ❌ Hayır — metot bilinmeli |
Kalıtım (class Car : public Vehicle) | ❌ Hayır — tam tanım gerekli |
Basit kural: pointer veya referans yetiyorsa forward declaration kullan, nesnenin kendisiyle iş yapacaksan include et.
Circular Dependency Çözümü
İki sınıfın birbirine referans verdiği duruma bakalım:
// PROBLEM: a.h includes b.h, b.h includes a.h → SONSUZ DÖNGÜ
// a.h
#pragma once
#include "b.h" // b.h'ı dahil et
class A {
B* partner; // B'ye pointer
};
// b.h
#pragma once
#include "a.h" // a.h'ı dahil et ← SORUN!
class B {
A* partner; // A'ya pointer
};Bu derlenmez! Preprocessor sonsuz döngüye girer (guard sayesinde döngü biter ama sıralama bozulur). Çözüm:
// ÇÖZÜM: Forward declaration kullan
// a.h
#pragma once
class B; // Forward declaration
class A {
public:
void setPartner(B* b);
private:
B* partner;
};
// b.h
#pragma once
class A; // Forward declaration
class B {
public:
void setPartner(A* a);
private:
A* partner;
};
// a.cpp
#include "a.h"
#include "b.h" // Burada include ediyoruz — .cpp dosyasında sorun yok
void A::setPartner(B* b) { partner = b; }
// b.cpp
#include "b.h"
#include "a.h"
void B::setPartner(A* a) { partner = a; }Header'larda forward declaration, source (.cpp) dosyalarında tam include. Bu pattern çok yaygındır ve profesyonel projelerde standart bir yaklaşımdır.
Include Sırası Best Practices
Header'ları hangi sırayla dahil ettiğin önemlidir. Doğru sıralama, gizli bağımlılıkları erken tespit etmeni sağlar.
Önerilen Sıra
// main.cpp veya herhangi bir .cpp dosyası
// 1. İlişkili header (bu .cpp'nin kendi header'ı)
#include "game.h"
// 2. C standart kütüphane header'ları
#include <cstdlib>
#include <cstring>
// 3. C++ standart kütüphane header'ları
#include <iostream>
#include <string>
#include <vector>
// 4. Üçüncü parti kütüphane header'ları
#include <SFML/Graphics.hpp>
#include <nlohmann/json.hpp>
// 5. Proje header'ları
#include "engine/renderer.h"
#include "utils/logger.h"İlk sıraya kendi header'ını koymak neden önemli? Çünkü eğer game.h bir bağımlılığı eksik bırakmışsa (mesela <string> dahil etmeyi unutmuşsa), hata hemen ortaya çıkar. Eğer <string> daha önce dahil edilmişse, bu hata gizlenir ve başka birisi game.h'ı farklı bir sırayla dahil ettiğinde patlak verir.
Alfabetik Sıralama
Her grup içinde header'ları alfabetik sıralaman, okunabilirliği artırır ve merge conflict'lerini azaltır. Birçok linter ve formatter (clang-format gibi) bunu otomatik yapar.
Compilation Unit / Translation Unit
Bir C++ programı derlenirken, her .cpp dosyası bağımsız olarak derlenir. Her .cpp dosyasına (ve onun dahil ettiği tüm header'lara) bir translation unit (derleme birimi) denir.
player.cpp ─→ [preprocessor] ─→ [compiler] ─→ player.o
game.cpp ─→ [preprocessor] ─→ [compiler] ─→ game.o
main.cpp ─→ [preprocessor] ─→ [compiler] ─→ main.o
player.o + game.o + main.o ─→ [linker] ─→ program.exeBu akışı anlamak önemli çünkü:
Her
.cppdosyası diğerlerinden habersiz derlenir. Birinde tanımlı bir fonksiyon, diğerinde sadece "bildirim" (declaration) yoluyla bilinir — bu yüzden header dosyaları gereklidir.Linker, tüm
.o(object) dosyalarını birleştirir. Eğer bir fonksiyon bildirilmiş ama hiçbir yerde tanımlanmamışsa, linker "undefined reference" hatası verir. Eğer birden fazla yerde tanımlanmışsa, "multiple definition" hatası verir.Header dosyaları derlenmez — sadece dahil edildikleri
.cppdosyasının bir parçası olurlar. Yaniplayer.htek başına bir translation unit değildir;player.cpp'ye (ve onu include eden diğer dosyalara) "yapıştırılır."
One Definition Rule (ODR)
C++ kurallarına göre her fonksiyon, değişken veya sınıf tüm programda sadece bir kez tanımlanabilir (bazı istisnalar dışında). Header'da bir fonksiyonun gövdesini yazarsan ve iki farklı .cpp dosyası bu header'ı dahil ederse, aynı fonksiyon iki kez tanımlanmış olur.
// utils.h — SORUNLU
#pragma once
int add(int a, int b) { // TANIM — header'da!
return a + b;
}Eğer a.cpp ve b.cpp ikisi de utils.h'ı include ederse: add fonksiyonu iki kez tanımlanır → linker hatası!
Çözümler:
// Çözüm 1: Header'da sadece bildirim, .cpp'de tanım
// utils.h
#pragma once
int add(int a, int b); // Sadece bildirim
// utils.cpp
#include "utils.h"
int add(int a, int b) { return a + b; } // Tanım// Çözüm 2: inline anahtar kelimesi
// utils.h
#pragma once
inline int add(int a, int b) { // inline — birden fazla tanıma izin verir
return a + b;
}// Çözüm 3: constexpr (C++11+, zaten inline kabul edilir)
// utils.h
#pragma once
constexpr int add(int a, int b) {
return a + b;
}Sınıf tanımları (class body) ve template'ler ODR'ın istisnasıdır — bunlar header'da tanımlanabilir (ve genellikle tanımlanmalıdır). Sınıf gövdesi içinde tanımlanan metotlar otomatik olarak inline kabul edilir.
Pratik Örnek: 3 Dosyalı Proje
Şimdi öğrendiklerimizi bir araya getirelim. Basit bir hesap makinesi projesi yapacağız:
Dosya Yapısı
calculator/
├── calculator.h ← Bildirimler
├── calculator.cpp ← Tanımlar
└── main.cpp ← Giriş noktasıcalculator.h
#pragma once
#include <string>
// Forward declaration — sadece pointer/referans kullanıyorsak yeterli
// Bu örnekte gerekmese de konsepti göstermek için burada
class Calculator {
public:
Calculator();
double add(double a, double b) const;
double subtract(double a, double b) const;
double multiply(double a, double b) const;
double divide(double a, double b) const;
// Son işlemin sonucunu döndürür
double getLastResult() const;
// İşlem geçmişini string olarak döndürür
std::string getHistory() const;
private:
double lastResult_;
std::string history_;
void recordOperation(const std::string& op, double a, double b, double result);
};calculator.cpp
#include "calculator.h" // 1. Kendi header'ımız (ilk sırada!)
#include <sstream> // 2. Standart kütüphane
#include <stdexcept>
Calculator::Calculator()
: lastResult_(0.0), history_("") {}
double Calculator::add(double a, double b) const {
return a + b;
}
double Calculator::subtract(double a, double b) const {
return a - b;
}
double Calculator::multiply(double a, double b) const {
return a * b;
}
double Calculator::divide(double a, double b) const {
if (b == 0.0) {
throw std::invalid_argument("Sifira bolme hatasi!");
}
return a / b;
}
double Calculator::getLastResult() const {
return lastResult_;
}
std::string Calculator::getHistory() const {
return history_;
}
void Calculator::recordOperation(const std::string& op,
double a, double b, double result) {
std::ostringstream oss;
oss << a << " " << op << " " << b << " = " << result << "\n";
history_ += oss.str();
lastResult_ = result;
}main.cpp
#include "calculator.h" // 1. Proje header
#include <iostream> // 2. Standart kütüphane
int main() {
Calculator calc;
double a = 10.0, b = 3.0;
std::cout << a << " + " << b << " = " << calc.add(a, b) << std::endl;
std::cout << a << " - " << b << " = " << calc.subtract(a, b) << std::endl;
std::cout << a << " * " << b << " = " << calc.multiply(a, b) << std::endl;
std::cout << a << " / " << b << " = " << calc.divide(a, b) << std::endl;
// Sifira bolme denemesi
try {
calc.divide(5.0, 0.0);
} catch (const std::invalid_argument& e) {
std::cout << "Hata: " << e.what() << std::endl;
}
return 0;
}Derleme
# Her .cpp ayrı derlenir (translation unit)
g++ -c calculator.cpp -o calculator.o
g++ -c main.cpp -o main.o
# Object dosyaları birleştirilir (linking)
g++ calculator.o main.o -o calculator
# Veya tek satırda:
g++ calculator.cpp main.cpp -o calculatorBu küçük örnekte bile tüm kavramlar devrede:
#pragma once→ çift dahil etmeyi önlüyorHeader/source ayrımı → bildirim ve tanım farklı dosyalarda
Include sırası → kendi header'ımız ilk sırada
Translation unit → her
.cppbağımsız derleniyor
Koşullu Derleme Detayları
Preprocessor'ın en güçlü özelliklerinden biri, kodun belirli bölümlerini koşullara göre dahil edip çıkarabilmesidir. Bu, farklı platformlar, debug/release modları veya özellik açma/kapama (feature toggles) için kullanılır.
#ifdef ve #ifndef
// DEBUG sembolü tanımlıysa bu blok derlenir
#ifdef DEBUG
std::cout << "Debug modu aktif" << std::endl;
#endif
// PRODUCTION tanımlı DEĞİLse bu blok derlenir
#ifndef PRODUCTION
// Test kodu
runTests();
#endif#if, #elif, #else
Daha karmaşık koşullar için:
#if defined(_WIN32)
// Windows kodu
#include <windows.h>
void sleepMs(int ms) { Sleep(ms); }
#elif defined(__linux__)
// Linux kodu
#include <unistd.h>
void sleepMs(int ms) { usleep(ms * 1000); }
#elif defined(__APPLE__)
// macOS kodu
#include <unistd.h>
void sleepMs(int ms) { usleep(ms * 1000); }
#else
#error "Desteklenmeyen platform!"
#endifVersiyon Kontrolü
// C++ standardı versiyonunu kontrol et
#if __cplusplus >= 202002L
// C++20 özellikleri kullanılabilir
#include <concepts>
#elif __cplusplus >= 201703L
// C++17 özellikleri
#include <optional>
#elif __cplusplus >= 201402L
// C++14 özellikleri
#elif __cplusplus >= 201103L
// C++11 özellikleri
#else
#error "En az C++11 gerekli!"
#endifDerleyici Tanımlı Makrolar
Her derleyici otomatik olarak bazı makroları tanımlar:
#include <iostream>
int main() {
std::cout << "Dosya: " << __FILE__ << std::endl;
std::cout << "Satir: " << __LINE__ << std::endl;
std::cout << "Tarih: " << __DATE__ << std::endl;
std::cout << "Saat: " << __TIME__ << std::endl;
std::cout << "Fonksiyon: " << __func__ << std::endl; // C++11
std::cout << "C++ Standardi: " << __cplusplus << std::endl;
return 0;
}Bu makrolar özellikle loglama ve hata ayıklama için çok kullanışlıdır.
Pragma Direktifleri
#pragma, derleyiciye özel talimatlar vermek için kullanılır. Standart değildir — her derleyici farklı pragma'ları destekleyebilir.
// En yaygın pragma — header guard
#pragma once
// MSVC: uyarı bastırma
#pragma warning(disable : 4996) // Deprecated fonksiyon uyarısını kapat
// GCC/Clang: uyarı bastırma
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
// ... burada uyarı çıkmaz ...
#pragma GCC diagnostic pop // Önceki uyarı durumuna geri dön
// Yapı hizalama (structure packing)
#pragma pack(push, 1) // 1 byte hizalama
struct NetworkPacket {
uint8_t type;
uint32_t data;
}; // Normalde 8 byte olurdu, şimdi 5 byte
#pragma pack(pop)⚠️ Dikkat:
#pragmakullanırken dikkatli ol. Uyarıları bastırmak genellikle kötü bir pratiktir — uyarılar bir sebepten vardır.#pragma packise performansı olumsuz etkileyebilir. Sadece ne yaptığını bildiğinde kullan.
Sık Yapılan Hatalar
1. Header'da using namespace
// utils.h — KÖTÜ!
#pragma once
#include <string>
using namespace std; // ← Bu header'ı include eden HER dosyayı etkiler!
string formatName(const string& name);Header'da using namespace std; kullanmak, o header'ı dahil eden tüm dosyalarda std namespace'ini açar. Bu isim çakışmalarına neden olabilir. Header dosyalarında her zaman tam nitelikli isimler (std::string) kullan.
2. Header'da Değişken Tanımlama
// config.h — KÖTÜ!
#pragma once
int maxRetries = 3; // Her include eden dosyada ayrı bir kopya!Birden fazla .cpp dosyası bu header'ı dahil ederse, linker "multiple definition" hatası verir. Çözüm:
// config.h — İYİ
#pragma once
extern int maxRetries; // Sadece bildirim
// veya
constexpr int maxRetries = 3; // constexpr → inline → ODR sorunsuz
// veya (C++17)
inline int maxRetries = 3; // inline değişken3. Gereksiz Include Zinciri
// Her dosya sadece ihtiyacını include etmeli
// a.h
#pragma once
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <iostream> // ← Gerçekten gerekli mi?İhtiyacın olmayan header'ları dahil etmek derleme süresini uzatır. Büyük projelerde bu fark dakikalar mertebesinde olabilir. Sadece gerçekten kullandığın header'ları dahil et.
Preprocessor Çıktısını Görmek
Preprocessor'ın kodunla ne yaptığını görmek istiyorsan, -E bayrağıyla derleyiciyi çalıştırabilirsin:
# Preprocessor çıktısını ekrana yaz
g++ -E main.cpp
# Dosyaya yaz
g++ -E main.cpp -o main.i
# Sadece makroları listele
g++ -dM -E main.cppÇıktı çok uzun olabilir (binlerce satır) çünkü tüm include edilen header'ların içeriği dahil edilir. Ama preprocessor'ın davranışını anlamak için harika bir araçtır.
Özet
Preprocessor, derleyiciden önce çalışan bir metin işleme aracıdır.
#ile başlayan direktiflerle kontrol edilir.#include dosya içeriğini bulunduğu yere yapıştırır.
<>sistem header'ları,""proje header'ları içindir.Makrolar (#define) güçlü ama tehlikelidir. Sabitler için
constexpr, fonksiyon makroları içininlineveyaconstexprfonksiyonları tercih et. Makroları sadece koşullu derleme ve header guard'lar için kullan.Header guard'lar (
#ifndefpattern veya#pragma once) çift dahil etmeyi önler. Modern projelerde#pragma oncetercih edilir.Forward declaration, derleme hızını artırır ve döngüsel bağımlılıkları çözer. Pointer veya referans yetiyorsa include yerine forward declaration kullan.
Include sırası (ilişkili header → sistem → üçüncü parti → proje) gizli bağımlılıkları erken tespit etmeye yardımcı olur.
Translation unit, bir
.cppdosyası ve dahil ettiği tüm header'lardan oluşur. Her translation unit bağımsız derlenir, sonra linker birleştirir.
AI Asistan
Sorularını yanıtlamaya hazır