Scope, Duration ve Linkage — Derinlemesine
Bir değişken tanımladığında aklına üç soru gelmeli: Bu değişkeni nereden görebilirim (scope)? Bu değişken ne kadar yaşar (storage duration)? Bu değişkene başka dosyalardan erişebilir miyim (linkage)? Bu üç kavram C++'ın bellek modelinin temelini oluşturur.
Çoğu başlangıç kaynağı bu konuları yüzeysel geçer, ama büyük projeler yazdığında — özellikle çok dosyalı (multi-file) projelerde — scope, duration ve linkage anlayışın sağlam olmalı. Aksi takdirde "neden bu değişkeni göremiyorum?", "neden linker hatası alıyorum?" veya "bu değişken neden sıfırlanmadı?" gibi sorularla boğuşursun.
Analoji: Bir üniversite kampüsünü düşün. Scope, bir kişinin hangi odalara/binalara girebildiğidir (kartın nereleri açar?). Storage duration, bir odanın ne kadar süre tahsis edildiğidir (bir dönemlik mi, mezuniyete kadar mı, kalıcı mı?). Linkage ise bir odanın diğer kampüslerden (dosyalardan) erişilebilir olup olmadığıdır (sadece bu kampüste mi geçerli, yoksa şehirdeki tüm kampüslerden mi erişilebilir?).
Scope Türleri
Scope (kapsam), bir ismin (değişken, fonksiyon, tip) kodun hangi bölümünden erişilebilir olduğunu belirler.
Block Scope (Blok Kapsamı)
Süslü parantezler {} arasında tanımlanan isimler block scope'a sahiptir. Tanımlandığı noktadan bloğun sonuna kadar görünürdür.
#include <iostream>
int main() {
int x = 10; // main bloğunun scope'unda
{
int y = 20; // iç bloğun scope'unda
std::cout << x << std::endl; // OK — x dış blokta
std::cout << y << std::endl; // OK — y bu blokta
}
std::cout << x << std::endl; // OK
// std::cout << y << std::endl; // HATA — y artık scope dışı!
// for döngüsü de bir blok oluşturur
for (int i = 0; i < 5; i++) {
int temp = i * 2; // temp sadece bu döngü bloğunda
std::cout << temp << " ";
}
// i ve temp burada erişilemez
return 0;
}Block scope en yaygın scope türüdür. Yerel değişkenler, döngü değişkenleri ve if blokları içindeki değişkenler hep block scope'tadır.
İç blok dış bloğu gölgeler (shadowing):
int x = 10;
{
int x = 20; // Dış x'i gölgeler — tehlikeli!
std::cout << x; // 20 yazdırır
}
std::cout << x; // 10 yazdırır — dış x sağlam⚠️ Dikkat: Shadowing (gölgeleme) yasal ama tehlikelidir. Dış scope'taki bir değişkenle aynı isimde iç değişken tanımlarsan, derleyici uyarı verebilir ama hata vermez. Bu tür hatalar bulmak zordur. Derleyicini
-Wshadowflag'i ile çalıştır — shadowing durumlarını uyarı olarak gösterecektir.
Function Scope (Fonksiyon Kapsamı)
C++'ta function scope yalnızca goto label'ları için geçerlidir. Bir label, tanımlandığı fonksiyonun her yerinden erişilebilir:
void fonksiyon() {
goto son; // Label henüz tanımlanmadı ama erişilebilir
std::cout << "Bu satır atlanır" << std::endl;
son: // function scope — fonksiyonun her yerinden görünür
std::cout << "Son!" << std::endl;
}Pratikte goto kullanmadığın sürece function scope ile karşılaşmazsın. Ve goto kullanma. Gerçekten. Sadece çok özel durumlarda (error cleanup gibi C tarzı kod) düşün.
Namespace Scope (İsim Alanı Kapsamı)
Bir namespace içinde tanımlanan isimler, o namespace'in scope'undadır:
namespace Matematik {
const double PI = 3.14159265358979;
double karesiniAl(double x) {
return x * x;
}
namespace Trigonometri {
double derecedenRadyana(double derece) {
return derece * PI / 180.0;
}
}
}
int main() {
// Tam nitelikli isim (fully qualified name)
double sonuc = Matematik::karesiniAl(5.0);
double rad = Matematik::Trigonometri::derecedenRadyana(90.0);
// using ile kısaltma
using namespace Matematik;
double alan = PI * karesiniAl(3.0);
return 0;
}Global namespace (dosyanın en üst seviyesi) de bir namespace'tir — global namespace scope diye adlandırılır.
File Scope (Dosya Kapsamı)
Herhangi bir fonksiyon veya sınıfın dışında, dosyanın en üst seviyesinde tanımlanan isimler file scope'a (aslında global namespace scope'a) sahiptir. Bu isimler dosyanın o noktasından itibaren dosyanın sonuna kadar görünürdür.
// file_scope_ornek.cpp
int globalSayac = 0; // File scope — dosyanın her yerinden erişilebilir
void artir() {
globalSayac++; // OK
}
void yazdir() {
std::cout << globalSayac << std::endl; // OK
}Class Scope (Sınıf Kapsamı)
Bir sınıf içinde tanımlanan isimler class scope'tadır. Erişim belirleyicileri (public, private, protected) görünürlüğü etkiler, ama scope aynıdır:
class Ogrenci {
private:
std::string isim_; // class scope
int numara_; // class scope
public:
Ogrenci(const std::string& isim, int no)
: isim_(isim), numara_(no) {}
void bilgiYazdir() const { // class scope
// Aynı scope'taki private üyelere erişebilir
std::cout << isim_ << " (" << numara_ << ")" << std::endl;
}
};
int main() {
Ogrenci o("Ahmet", 123);
o.bilgiYazdir(); // OK — public
// o.isim_; // HATA — private
return 0;
}Class scope'un özel bir özelliği: üye fonksiyonlar, sınıfta kendilerinden sonra tanımlanan üyelere de erişebilir. Bu, diğer scope'lardan farklıdır (block scope'ta bir değişken tanımlanmadan önce kullanılamaz).
Storage Duration (Depolama Süresi)
Storage duration, bir nesnenin bellekte ne kadar süre var olduğunu belirler. C++'ta dört tür storage duration vardır.
Automatic Storage Duration
Block scope'taki değişkenlerin varsayılan süresidir. Blok başlayınca oluşturulur, blok bitince yok edilir.
void fonksiyon() {
int x = 42; // Automatic — fonksiyon bitince yok olur
std::string s = "merhaba"; // Aynı — destructor çağrılır
if (true) {
std::vector<int> v = {1, 2, 3}; // Blok bitince yok olur
} // v'nin destructor'ı burada çağrılır
} // x ve s burada yok olurAutomatic nesneler stack üzerinde yaşar. Oluşturulma ve yok edilme maliyeti neredeyse sıfırdır (sadece stack pointer'ı hareket eder). C++'ta "yerel değişken" dediğimiz şeyler genellikle automatic storage duration'a sahiptir.
Static Storage Duration
Program başlayınca oluşturulur, program bitene kadar yaşar. Üç şekilde elde edilir:
// 1. Global/namespace scope değişkenler
int globalSayac = 0; // Static duration — otomatik
// 2. static anahtar kelimesiyle yerel değişkenler
void sayac() {
static int count = 0; // İlk çağrıda oluşturulur, sonra yaşamaya devam eder
count++;
std::cout << "Çağrı #" << count << std::endl;
}
// 3. static sınıf üyeleri
class Logger {
static int instanceCount; // Tüm Logger nesneleri paylaşır
};
int Logger::instanceCount = 0;Static değişkenler data segment veya BSS segment'te yaşar (stack'te değil). Başlangıç değeri verilmezse sıfırla başlatılır (zero-initialized).
Dynamic Storage Duration
new ile oluşturulan, delete ile yok edilen nesneler. Ömürleri programcının kontrolündedir.
void fonksiyon() {
int* p = new int(42); // Heap'te oluştur
std::string* s = new std::string("merhaba");
// ... kullan ...
delete p; // Manuel yok et
delete s; // Manuel yok et — destructor çağrılır
}
// Modern C++: akıllı pointer'lar ile — delete'i unut
void modernFonksiyon() {
auto p = std::make_unique<int>(42);
auto s = std::make_unique<std::string>("merhaba");
// Fonksiyon bitince otomatik delete edilir
}Dynamic nesneler heap (free store) üzerinde yaşar. Oluşturma ve yok etme maliyeti automatic'ten çok daha yüksektir.
Thread-Local Storage Duration
C++11 ile gelen thread_local, her thread'in kendi kopyasına sahip olduğu değişkenler oluşturur:
#include <iostream>
#include <thread>
thread_local int threadSayac = 0; // Her thread'in kendi kopyası
void worker(int id) {
for (int i = 0; i < 5; i++) {
threadSayac++;
std::cout << "Thread " << id << ": " << threadSayac << std::endl;
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
// Her thread kendi sayacını 1'den 5'e kadar sayar
// Birbirlerini etkilemezler
return 0;
}Thread-local değişkenler, her thread başlayınca oluşturulur ve thread bitince yok edilir. Global state'e ihtiyaç duyduğunda ama thread-safety istediğinde kullanışlıdır.
Linkage Türleri
Linkage (bağlama), bir ismin farklı scope'lar veya çeviri birimleri (translation units, yani .cpp dosyaları) arasında aynı varlığa mı yoksa farklı varlıklara mı işaret ettiğini belirler.
No Linkage (Bağlama Yok)
Block scope'taki değişkenler ve extern olmayan yerel değişkenler no linkage'dır. Sadece tanımlandıkları blokta erişilebilirler:
void fonksiyon() {
int x = 10; // No linkage — sadece bu blokta
static int y = 20; // No linkage — yine sadece bu blokta
// (ama static duration!)
}Internal Linkage (İç Bağlama)
Internal linkage'a sahip isimler, tanımlandıkları çeviri biriminde (translation unit = bir .cpp dosyası ve include'ları) her yerden erişilebilir, ama başka .cpp dosyalarından erişilemez.
// helper.cpp
// static global değişken — internal linkage
static int secretCounter = 0;
// static fonksiyon — internal linkage
static void helperFunction() {
secretCounter++;
}
// const global değişken — C++'ta varsayılan olarak internal linkage!
const int MAX_SIZE = 100;
// Bu dosyadaki diğer fonksiyonlar secretCounter ve helperFunction'a
// erişebilir, ama başka .cpp dosyaları erişemez.Internal linkage elde etmenin yolları:
statickeyword'ü ile (global değişken veya fonksiyon)const/constexprglobal değişkenler (C++'ta varsayılan)Unnamed (isimsiz) namespace
External Linkage (Dış Bağlama)
External linkage'a sahip isimler, programdaki tüm çeviri birimlerinden erişilebilir. Fonksiyonlar ve const olmayan global değişkenler varsayılan olarak external linkage'dır.
// math_utils.cpp
int sharedCounter = 0; // External linkage (varsayılan)
void increment() { // External linkage (varsayılan)
sharedCounter++;
}// main.cpp
extern int sharedCounter; // Başka dosyadaki değişkeni bildir
extern void increment(); // Fonksiyonlar için extern opsiyonel ama açık
int main() {
increment();
std::cout << sharedCounter << std::endl; // 1
return 0;
}extern keyword'ü declaration (bildirim) yapar, definition (tanımlama) değil. Derleyiciye "bu isim başka yerde tanımlı, onu kullan" der.
Static Keyword'ün 3 Farklı Anlamı
C++'ta static keyword'ü bağlama göre üç tamamen farklı anlama gelir. Bu, yeni başlayanlar için en kafa karıştıran noktalardan biridir.
1. Static Local Variable — Kalıcı Yerel Değişken
Fonksiyon içinde static ile tanımlanan değişken, fonksiyon çağrıları arasında değerini korur:
#include <iostream>
void sayac() {
static int count = 0; // Sadece bir kez başlatılır!
count++;
std::cout << "Çağrı sayısı: " << count << std::endl;
}
int main() {
sayac(); // Çağrı sayısı: 1
sayac(); // Çağrı sayısı: 2
sayac(); // Çağrı sayısı: 3
// count hâlâ block scope'ta — dışarıdan erişilemez
return 0;
}Bu, state tutma ve lazy initialization için çok kullanışlıdır:
#include <iostream>
#include <string>
// Lazy initialization — ihtiyaç duyulduğunda oluştur
const std::string& getConfig() {
static std::string config = loadFromFile("config.txt"); // Sadece ilk çağrıda
return config;
}
// Singleton pattern — static local ile
class Database {
public:
static Database& instance() {
static Database db; // Thread-safe (C++11 garantisi)
return db;
}
void query(const std::string& sql) {
std::cout << "Sorgu: " << sql << std::endl;
}
private:
Database() { std::cout << "DB bağlantısı kuruldu." << std::endl; }
Database(const Database&) = delete;
Database& operator=(const Database&) = delete;
};C++11'den itibaren static local variable'ların başlatılması thread-safe'dir. İki thread aynı anda instance() çağırırsa, sadece biri Database nesnesini oluşturur, diğeri bekler. Buna "magic static" denir.
2. Static at File Scope — Internal Linkage
Dosyanın en üst seviyesinde (fonksiyon dışında) static, değişkene veya fonksiyona internal linkage verir:
// utils.cpp
// Bu değişken sadece utils.cpp'den erişilebilir
static int internalCounter = 0;
// Bu fonksiyon sadece utils.cpp'den çağrılabilir
static void internalHelper() {
internalCounter++;
}
// Bu fonksiyon dışarıdan erişilebilir (external linkage)
void publicFunction() {
internalHelper();
}Bu kullanım, bir dosyanın "uygulama detaylarını" dış dünyadan gizlemek için kullanılır. C'de çok yaygındır, C++'ta unnamed namespace tercih edilir (birazdan göreceğiz).
3. Static Class Member — Sınıf Seviyesinde Paylaşılan Üye
Sınıf üyeleri önüne static koyduğunda, o üye nesneye değil sınıfa aittir. Tüm nesneler aynı değişkeni/fonksiyonu paylaşır:
#include <iostream>
class Player {
std::string name_;
static int playerCount_; // Tüm Player nesneleri paylaşır
public:
Player(const std::string& name) : name_(name) {
playerCount_++;
std::cout << name_ << " katıldı. Toplam: "
<< playerCount_ << std::endl;
}
~Player() {
playerCount_--;
std::cout << name_ << " ayrıldı. Toplam: "
<< playerCount_ << std::endl;
}
static int getPlayerCount() { // Static member function
return playerCount_;
// NOT: static fonksiyon, non-static üyelere erişemez!
// name_ burada kullanılamaz.
}
};
// Static member tanımlaması — sınıf dışında yapılmalı!
int Player::playerCount_ = 0;
int main() {
Player p1("Ahmet"); // Ahmet katıldı. Toplam: 1
Player p2("Mehmet"); // Mehmet katıldı. Toplam: 2
std::cout << "Aktif oyuncu: " << Player::getPlayerCount() << std::endl;
{
Player p3("Ayşe"); // Ayşe katıldı. Toplam: 3
} // Ayşe ayrıldı. Toplam: 2
return 0;
}Üç farklı static kullanımını özetleyelim:
| Kullanım | Konum | Anlam |
|---|---|---|
static int x; | Fonksiyon içi | Kalıcı yerel değişken — çağrılar arası değer korur |
static int x; | Dosya seviyesi | Internal linkage — bu dosyaya özel |
static int x; | Sınıf içi | Sınıf seviyesi üye — tüm nesneler paylaşır |
Unnamed Namespace — Modern Internal Linkage
C'de internal linkage için static kullanılır. C++'ta ise daha modern ve önerilen yol unnamed (isimsiz) namespace'tir:
// C tarzı — çalışır ama eski
static int secretVar = 42;
static void secretFunction() { /* ... */ }
// C++ tarzı — önerilen
namespace {
int secretVar2 = 42;
void secretFunction2() { /* ... */ }
class InternalHelper { // Sınıfları da gizleyebilirsin!
// ...
};
}Unnamed namespace içindeki her şey, sanki static yazılmış gibi internal linkage'a sahip olur. Ama unnamed namespace'in avantajı: sınıfları, enum'ları ve typedef'leri de gizleyebilir — static bunu yapamaz.
// header_helpers.cpp
// Bu namespace sadece bu dosyada görünür
namespace {
// Yardımcı enum — dış dünyadan gizli
enum class LogLevel { Debug, Info, Warning, Error };
// Yardımcı sınıf — dış dünyadan gizli
class FormatHelper {
public:
static std::string format(LogLevel level, const std::string& msg) {
const char* prefixes[] = {"[DBG]", "[INF]", "[WRN]", "[ERR]"};
return std::string(prefixes[static_cast<int>(level)]) + " " + msg;
}
};
// Yardımcı fonksiyon — dış dünyadan gizli
void logInternal(LogLevel level, const std::string& msg) {
std::cout << FormatHelper::format(level, msg) << std::endl;
}
}
// Dış dünyaya açık fonksiyon
void logInfo(const std::string& msg) {
logInternal(LogLevel::Info, msg);
}
void logError(const std::string& msg) {
logInternal(LogLevel::Error, msg);
}💡 İpucu: Header dosyalarında (.h/.hpp) unnamed namespace kullanma! Header dosyaları birden fazla .cpp dosyasına include edilir. Her include eden dosya, unnamed namespace içindeki tanımlamaların kendi kopyasını alır — bu da ODR ihlali olmasa da gereksiz kod tekrarına ve büyük binary'ye yol açar.
ODR — One Definition Rule
C++'ın en temel kurallarından biri: tek tanım kuralı (One Definition Rule, ODR). Bu kural iki seviyede çalışır:
Seviye 1: Bir Çeviri Biriminde Tek Tanım
Bir .cpp dosyası içinde her şey en fazla bir kez tanımlanabilir:
// HATA — aynı dosyada iki kez tanımlama
int x = 10;
int x = 20; // Derleme hatası!
void foo() { }
void foo() { } // Derleme hatası!Seviye 2: Tüm Programda Tek Tanım
External linkage'a sahip fonksiyonlar ve değişkenler, tüm programda (tüm .cpp dosyaları toplamında) en fazla bir kez tanımlanabilir:
// a.cpp
int globalVar = 10; // Tanımlama
// b.cpp
int globalVar = 20; // HATA — linker hatası! Zaten a.cpp'de tanımlı// a.cpp
void foo() { std::cout << "a" << std::endl; }
// b.cpp
void foo() { std::cout << "b" << std::endl; } // HATA — linker hatası!ODR İhlali Nasıl Olur?
En yaygın senaryo: bir fonksiyonu header dosyasında tanımlayıp birden fazla .cpp'ye include etmek:
// utils.h — HATALI
#pragma once
int topla(int a, int b) {
return a + b;
}
// a.cpp
#include "utils.h" // topla burada tanımlanır
// b.cpp
#include "utils.h" // topla burada da tanımlanır — ODR ihlali!ODR'dan Kaçınma Yolları
1. Header'da bildirim, .cpp'de tanımlama (klasik yol):
// utils.h
#pragma once
int topla(int a, int b); // Bildirim (declaration)
// utils.cpp
#include "utils.h"
int topla(int a, int b) { // Tanımlama (definition)
return a + b;
}2. inline keyword'ü:
inline fonksiyonlar ODR'ın istisnasıdır — her çeviri biriminde tanımlanabilir, ama tüm tanımlar birebir aynı olmalıdır:
// utils.h
#pragma once
inline int topla(int a, int b) { // inline — ODR istisnası
return a + b;
}inline derleyiciye "bu fonksiyonu inline et" demek değildir (bu bir öneri, garanti değil). Asıl anlamı: "bu fonksiyon birden fazla çeviri biriminde tanımlanabilir." Template fonksiyonlar ve sınıf içinde tanımlanan üye fonksiyonlar örtük olarak inline'dır.
3. Template'ler — doğal olarak ODR uyumlu:
// utils.h
#pragma once
template<typename T>
T topla(T a, T b) {
return a + b;
}
// Template tanımları header'da durabilir — derleyici bunu yönetir4. constexpr fonksiyonlar:
// utils.h
#pragma once
constexpr int kare(int x) {
return x * x;
}
// constexpr fonksiyonlar örtük olarak inline'dırGlobal Değişkenler: Neden Kötü?
Global değişkenler (file scope, external linkage) programcılıkta genellikle "code smell" olarak kabul edilir. Neden?
Sorunlar
1. Gizli bağımlılıklar: Bir fonksiyon global değişkene eriştiğinde, fonksiyonun imzasından bu bağımlılığı göremezsin:
int globalConfig = 0;
// Bu fonksiyon globalConfig'e bağımlı — ama imzadan belli değil!
void processData(const std::vector<int>& data) {
if (globalConfig == 1) {
// mod A
} else {
// mod B
}
}2. Test zorluğu: Global state nedeniyle testler birbirini etkiler. Bir test globalConfig'i değiştirirse, sonraki testler etkilenir.
3. Thread-safety: Global değişkenlere birden fazla thread erişebilir — race condition riski.
4. Başlatma sırası problemi (Static Initialization Order Fiasco):
// a.cpp
int x = 42;
// b.cpp
extern int x;
int y = x + 1; // y 43 mü? GARANTİ DEĞİL!
// x, y'den önce başlatılacak diye bir kural yok
// (farklı çeviri birimlerinde başlatma sırası tanımsız)Bu, C++'ın en sinsi bug'larından biridir. Farklı .cpp dosyalarındaki global değişkenlerin başlatma sırası tanımsızdır. x'in 42 ile mi yoksa 0 (zero-initialized) ile mi geleceği belirsizdir.
Çözüm: Construct-on-First-Use Idiom
Static Initialization Order Fiasco'nun klasik çözümü:
// KÖTÜ — global değişken
// int globalConfig = loadConfig();
// İYİ — ilk kullanımda oluştur
int& getGlobalConfig() {
static int config = loadConfig(); // İlk çağrıda başlatılır
return config;
}
// Kullanım
void doSomething() {
int cfg = getGlobalConfig(); // Sıra garanti!
}Bu pattern, global değişkeni static local'a dönüştürür. Böylece başlatma sırası garantidir: fonksiyon ilk çağrıldığında başlatılır.
Singleton Pattern — Kontrollü Global Erişim
Global state'e gerçekten ihtiyaç varsa (logger, konfigürasyon, veritabanı bağlantısı), Singleton pattern kullan:
class AppConfig {
public:
static AppConfig& instance() {
static AppConfig config;
return config;
}
int getMaxConnections() const { return maxConnections_; }
void setMaxConnections(int n) { maxConnections_ = n; }
std::string getDbHost() const { return dbHost_; }
void setDbHost(const std::string& host) { dbHost_ = host; }
private:
AppConfig() : maxConnections_(10), dbHost_("localhost") {
// Konfigürasyonu yükle
}
AppConfig(const AppConfig&) = delete;
AppConfig& operator=(const AppConfig&) = delete;
int maxConnections_;
std::string dbHost_;
};
// Kullanım
void connectDB() {
auto& config = AppConfig::instance();
std::cout << "Bağlanıyor: " << config.getDbHost()
<< " (max: " << config.getMaxConnections() << ")"
<< std::endl;
}Singleton da aslında "glorified global" dır — ama en azından başlatma sırası kontrollü, erişim noktası tek ve açık.
extern ile Değişken Paylaşımı (Multi-File)
Büyük projelerde birden fazla .cpp dosyası arasında değişken paylaşmak gerekebilir. extern keyword'ü bunun için kullanılır.
Temel Kullanım
// constants.h
#pragma once
extern const int MAX_PLAYERS; // Bildirim — tanım değil
extern const double GRAVITY; // Bildirim — tanım değil
extern std::string appVersion; // Bildirim — tanım değil
// constants.cpp
#include "constants.h"
const int MAX_PLAYERS = 100; // Tanımlama
const double GRAVITY = 9.81; // Tanımlama
std::string appVersion = "2.1.0"; // Tanımlama
// game.cpp
#include "constants.h"
void setupGame() {
std::cout << "Max oyuncu: " << MAX_PLAYERS << std::endl;
std::cout << "Yerçekimi: " << GRAVITY << std::endl;
}
// ui.cpp
#include "constants.h"
void showVersion() {
std::cout << "Versiyon: " << appVersion << std::endl;
}Kural: extern ile header'da bildirim yap, tam olarak bir .cpp dosyasında tanımlama yap.
const ve extern İlişkisi
C++'ta const global değişkenler varsayılan olarak internal linkage'dır (C'den farklı!). Eğer const değişkeni başka dosyalardan erişilebilir yapmak istersen, extern eklemelisin:
// YANLIŞ — her .cpp kendi kopyasını alır (internal linkage)
// constants.h
const int MAX_SIZE = 100; // Her include eden dosya kendi kopyasını üretir
// DOĞRU — tek bir tanımlama paylaşılır
// constants.h
extern const int MAX_SIZE; // Bildirim
// constants.cpp
extern const int MAX_SIZE = 100; // Tanımlama (extern ile external linkage)Aslında header'daki const ile her dosyanın kendi kopyasını alması çoğu durumda sorun değildir — derleyici zaten optimize eder. Ama büyük nesnelerde (büyük array'ler, string'ler) gereksiz bellek kullanımına yol açabilir.
Inline Variables (C++17)
C++17 öncesinde, header dosyasında global değişken tanımlamak ODR ihlaliydi. extern + ayrı .cpp dosyası gerekiyordu. C++17 ile gelen inline variables bu sorunu çözer:
// config.h — C++17
#pragma once
#include <string>
// inline variable — header'da tanımlanabilir, ODR ihlali yok!
inline const int MAX_BUFFER_SIZE = 4096;
inline const double PI = 3.14159265358979;
inline std::string DEFAULT_HOST = "localhost";inline keyword'ü, tıpkı inline fonksiyonlarda olduğu gibi, derleyiciye "bu tanım birden fazla çeviri biriminde görünebilir, ama hepsi aynıdır — sadece bir tane tut" der.
Inline Static Class Members
C++17 öncesinde static class member'ları sınıf dışında ayrı tanımlamak zorunluydu:
// C++14 ve öncesi
// player.h
class Player {
static int count; // Bildirim
};
// player.cpp
int Player::count = 0; // Tanımlama — ayrı dosya gerekli!C++17 ile inline kullanarak header'da tanımlayabilirsin:
// C++17
// player.h
class Player {
inline static int count = 0; // Hem bildirim hem tanımlama!
};
// Ayrı .cpp dosyası gerekmezBu, özellikle header-only kütüphaneler yazarken çok kullanışlıdır.
constexpr Static Members
C++17'de constexpr static member'lar örtük olarak inline'dır:
class MathConstants {
public:
static constexpr double PI = 3.14159265358979;
static constexpr double E = 2.71828182845904;
static constexpr int MAX_ITERATIONS = 1000;
// Hepsi örtük inline — ayrı tanımlama gerekmez
};Kapsamlı Örnek: Multi-File Proje
Tüm kavramları bir araya getiren küçük bir proje yapısı görelim:
// === game_config.h ===
#pragma once
#include <string>
// Inline variables (C++17) — header'da tanım
inline constexpr int SCREEN_WIDTH = 800;
inline constexpr int SCREEN_HEIGHT = 600;
inline const std::string GAME_TITLE = "Space Invaders";
// extern bildirim — tanım .cpp'de
extern int highScore;
// Singleton erişim
class GameConfig {
public:
static GameConfig& instance();
int getDifficulty() const { return difficulty_; }
void setDifficulty(int d) { difficulty_ = d; }
private:
GameConfig() : difficulty_(1) {}
int difficulty_;
};// === game_config.cpp ===
#include "game_config.h"
// extern değişken tanımlaması
int highScore = 0;
// Singleton tanımlaması
GameConfig& GameConfig::instance() {
static GameConfig config; // Magic static — thread-safe
return config;
}// === utils.h ===
#pragma once
#include <string>
// Public API — external linkage
std::string formatScore(int score);
void logMessage(const std::string& msg);// === utils.cpp ===
#include "utils.h"
#include <iostream>
#include <sstream>
#include <iomanip>
// Internal (unnamed namespace) — bu dosyaya özel
namespace {
const std::string LOG_PREFIX = "[GAME] ";
std::string padLeft(const std::string& s, int width) {
if (static_cast<int>(s.size()) >= width) return s;
return std::string(width - s.size(), ' ') + s;
}
}
// Public fonksiyonlar
std::string formatScore(int score) {
std::ostringstream oss;
oss << std::setfill('0') << std::setw(8) << score;
return oss.str();
}
void logMessage(const std::string& msg) {
static int messageCount = 0; // Static local — çağrılar arası sayaç
messageCount++;
std::cout << LOG_PREFIX << "[" << messageCount << "] " << msg << std::endl;
}// === main.cpp ===
#include "game_config.h"
#include "utils.h"
#include <iostream>
int main() {
// Inline constexpr — doğrudan kullan
std::cout << GAME_TITLE << " (" << SCREEN_WIDTH << "x"
<< SCREEN_HEIGHT << ")" << std::endl;
// extern değişken
highScore = 50000;
std::cout << "High Score: " << formatScore(highScore) << std::endl;
// Singleton
GameConfig::instance().setDifficulty(3);
std::cout << "Zorluk: " << GameConfig::instance().getDifficulty() << std::endl;
// Static local counter
logMessage("Oyun başlıyor");
logMessage("Seviye yüklendi");
logMessage("Oyuncu hazır");
return 0;
}Bu örnekte:
SCREEN_WIDTH,SCREEN_HEIGHT→ inline constexpr (header'da tanım)GAME_TITLE→ inline const (header'da tanım)highScore→ extern (header'da bildirim, .cpp'de tanım)GameConfig→ Singleton pattern (static local ile)LOG_PREFIX,padLeft→ unnamed namespace (internal linkage)messageCount→ static local (çağrılar arası state)
Kısa Referans Tablosu
| Tanımlama Yeri | Varsayılan Duration | Varsayılan Linkage |
|---|---|---|
Fonksiyon içi int x; | Automatic | No linkage |
Fonksiyon içi static int x; | Static | No linkage |
Dosya seviyesi int x; | Static | External |
Dosya seviyesi static int x; | Static | Internal |
Dosya seviyesi const int x; | Static | Internal (C++'a özel!) |
Dosya seviyesi extern const int x; | Static | External |
Dosya seviyesi inline int x; | Static | External |
namespace { int x; } | Static | Internal |
Sınıf içi static int x; | Static | External |
thread_local int x; | Thread | Bağlama göre değişir |
new int | Dynamic | — |
Özet
Scope bir ismin nereden erişilebilir olduğunu belirler. Block scope (en yaygın), namespace scope, class scope ve file scope ana türlerdir. Shadowing'e dikkat et —
-Wshadowflag'ini kullan.Storage duration bir nesnenin ne kadar yaşadığını belirler. Automatic (stack, blok sonunda ölür), static (program boyunca yaşar), dynamic (new/delete ile kontrol edilir) ve thread_local (her thread'e özel) olmak üzere dört türü vardır.
Linkage bir ismin başka çeviri birimlerinden erişilebilir olup olmadığını belirler. External linkage (tüm programdan erişilebilir), internal linkage (sadece bu dosyadan) ve no linkage (sadece bu bloktan) türleri vardır.
`static` keyword'ü bağlama göre üç farklı anlam taşır: fonksiyon içinde kalıcı yerel değişken, dosya seviyesinde internal linkage, sınıf içinde sınıfa ait paylaşılan üye. Hangisinin geçerli olduğunu yazıldığı yere bakarak belirle.
ODR (One Definition Rule) external linkage'a sahip her ismin tüm programda tek bir tanımı olmasını zorunlu kılar. Header'da fonksiyon tanımlama — bildirim yap, tanımı .cpp'ye koy. İstisna:
inline,template,constexpr.Inline variables (C++17) header-only tanımları mümkün kılar.
extern+ ayrı .cpp dosyası derdinden kurtarır. Static class member'lar içininline statickullanarak sınıf içinde tanımlama yapabilirsin.
AI Asistan
Sorularını yanıtlamaya hazır