MODERN C++ SMART POINTERLAR
"Smart pointer kullanıyorum, artık delete derdim yok" — C++ topluluğunda en sık duyulan ve en yarı doğru cümlelerden biri. Pek çok geliştirici unique_ptr ve shared_ptr'ı sihirli bir bellek temizleyicisi sanıyor, ama bu araçlar sadece kuralları kodlayan kaplardır. Sahiplik (ownership) modelini yanlış kurarsanız, smart pointer da sizi kurtarmaz: sızıntı yerine artık döngüsel referans, dangling iterator veya çifte serbest bırakma ile karşılaşırsınız. Bu yazıda yanılgıyı kırıp hangisi ne zaman sorusuna pratik bir cevap arıyoruz.
Yaygın Yanılgı: "Smart Pointer = Otomatik Bellek Yönetimi"
RAII güçlüdür ama büyü değildir. Smart pointer'lar, C++'ın stack-based deterministik yıkım davranışını heap nesnelerine taşır. Yani nesne, kendisini tutan akıllı işaretçi scope dışına çıktığında yok edilir. Buradaki kritik nokta: sahiplik kimde? sorusunu kod düzeyinde net cevaplamanız gerekir. Smart pointer bu cevabı sizin yerinize üretmez, sadece verdiğiniz cevabı uygular.
Sık karşılaşılan üç hatalı varsayım:
- "shared_ptr her şeyi çözer." Hayır — cycle (döngüsel referans) varsa nesneler ölmez.
- "unique_ptr kopyalanmıyorsa kullanışsız." Hayır —
std::moveile transfer edilir, bu zaten istenen davranıştır. - "raw pointer artık tehlikeli." Hayır — sahiplik içermeyen erişim (observer) için raw pointer hâlâ geçerlidir.
unique_ptr: Tek Sahip, Sıfır Maliyet
std::unique_ptr<T>, bir nesne üzerinde tek bir sahip olduğunu garantiler. Kopyalanamaz, sadece taşınabilir. Çalışma zamanı maliyeti pratikte sıfırdır — raw pointer ile aynı boyuttadır (custom deleter yoksa).
Tipik kullanım:
- Bir fonksiyonun yarattığı nesneyi çağırana tek elden devretmek:
std::unique_ptr<Widget> create(); - Sınıf üyesi olarak, bir kaynağın yaşam süresini sınıfın yaşam süresine kesin bağlamak.
- Polimorfik konteynerler:
std::vector<std::unique_ptr<Base>>— base pointer üzerinden virtual destructor ile doğru yıkım.
Sahiplik transferi açıkça std::move ile yapılır:
auto p = std::make_unique<Engine>(); car.attach(std::move(p)); // p artık nullptr

shared_ptr: Paylaşılan Sahiplik ve Gizli Maliyet
std::shared_ptr<T> birden fazla sahibin aynı nesneyi paylaşmasına izin verir. Arkasında bir control block vardır: strong count, weak count ve custom deleter saklanır. Son shared_ptr yok olunca nesne silinir.
Maliyet pratiktir ama görünmez değildir:
- Reference count atomic olduğundan her kopya/yıkım atomik bir işlem demektir.
- Pointer boyutu raw pointer'ın iki katıdır (nesne + control block adresi).
std::make_sharedtercih edilmeli — nesne ve control block tek allocation'da oluşur.
shared_ptr'ı reflexte kullanmak yerine "gerçekten paylaşılan sahiplik var mı?" sorusunu sorun. Çoğu zaman cevap hayır'dır ve unique_ptr + raw observer yeterlidir.
Cycle Tuzağı: shared_ptr'ın Sessiz Sızıntısı
İki nesne birbirini shared_ptr ile tutarsa, reference count asla sıfıra inmez. Parent → child ilişkilerinde sıkça görülür:
struct Node { std::shared_ptr<Node> next; std::shared_ptr<Node> prev; };
Burada prev weak_ptr olmalıdır. std::weak_ptr nesneye sahiplik almadan referans verir; erişmek için lock() ile geçici bir shared_ptr üretirsiniz. Bu, observer pattern'lerde ve cache'lerde de standart çözümdür.
Raw Pointer Hâlâ Yerinde: Non-Owning Access
Sahiplik içermeyen bir referansa ihtiyacınız varsa — bir fonksiyon parametresi olarak, kısa ömürlü bir gezinti için — raw pointer veya referans hâlâ doğru araçtır. Smart pointer'ı imza içine gömmek, çağıran fonksiyona yanlış bir sözleşme dayatmak demektir.
- void process(Widget* w) — "ben sahibi değilim, sadece bakıyorum."
- void process(std::unique_ptr<Widget> w) — "ben sahipliğini alıyorum."
- void process(const std::shared_ptr<Widget>& w) — "paylaşımlı kullanıma katılıyorum."
İmzanız niyetinizi anlatır. C++ Core Guidelines'ın F.7 ve R.30 kuralları bu konuda nettir: smart pointer parametreleri sadece sahiplik semantiği gerçekten gerekliyse kullanılır. Standart kütüphanenin bellek yönetimi başlıklarına ilişkin ayrıntılı başvuru dokümantasyonu bu kuralların arkasındaki API ayrımlarını izlemek için iyi bir referanstır.
Karar Akışı: Hangisini Seçmeli?
Pratik bir öncelik sırası:
- Önce stack üzerinde value semantiği mümkün mü? Mümkünse smart pointer'a hiç gerek yok.
- Heap gerekli ve tek sahip varsa →
unique_ptr. - Birden çok sahip gerçekten varsa →
shared_ptr. - Geri referans veya observer varsa →
weak_ptrya da raw pointer. - Polimorfik yıkım gerekiyorsa base sınıfta virtual destructor olduğundan emin olun.
C++'ın bu modern bellek araçlarını derinlemesine pratik etmek için Modern C++ eğitimi içeriklerinden yararlanabilirsiniz; ownership semantiği, move semantics ve RAII konuları orada bütünleşik anlatılır.
Performans Notları ve Sık Hatalar
Smart pointer hataları çoğunlukla derleme zamanında yakalanmaz; runtime'da, hatta production'da ortaya çıkar.
- this'i shared_ptr'a sarmayın doğrudan:
std::enable_shared_from_thiskullanın. - Aynı raw pointer'ı iki ayrı
shared_ptrile sarmak çifte yıkım demektir. get()ile elde ettiğiniz raw pointer'ı asladeleteetmeyin — sahiplik smart pointer'da.make_sharedkullanıyorsanız nesnenin belleği control block ile aynı blokta tutulur;weak_ptrhâlâ yaşıyorken nesne yok olsa bile bu blok serbest kalmaz — büyük nesnelerde dikkat.- Multi-threaded ortamda aynı
shared_ptrnesnesini farklı thread'lerden değiştirmek atomik değildir;atomic<shared_ptr<T>>(C++20) gerekir.

Sonuç olarak smart pointer'lar bellek yönetimini otomatikleştirmez — sahiplik kararlarınızı kodlayan disiplinli araçlardır. Doğru aracı doğru semantikle seçtiğinizde C++ kodunuz hem güvenli hem deterministik kalır; yanlış seçildiğinde ise raw pointer döneminden daha sessiz, daha derin hatalar üretirler. unique_ptr'ı varsayılan, shared_ptr'ı istisna, weak_ptr'ı kurtarıcı olarak düşünmek pratik bir başlangıçtır.



