İçeriğe geç

C++ Smart Pointer'lar: unique_ptr, shared_ptr ve weak_ptr Rehberi

T
Tolgahan
· · 14 dk okuma · 63 görüntülenme

title: "C++ Smart Pointer'lar: unique_ptr, shared_ptr ve weak_ptr Rehberi" slug: "cpp-smart-pointers-rehberi" category: "cpp" tags: ["cpp", "memory-management", "modern-cpp", "smart-pointers", "best-practices"] excerpt: "C++'ta bellek yönetiminin en kritik silahı olan smart pointer'ları baştan sona öğrenin. unique_ptr, shared_ptr ve weak_ptr'ın ne zaman, nasıl ve neden kullanılacağını gerçek örneklerle keşfedin." published_at: "2026-02-26"


C++ Smart Pointer'lar: unique_ptr, shared_ptr ve weak_ptr Rehberi

Diyelim ki bir arkadaşına kitabını ödünç verdin. İki ihtimal var: ya kitabı geri alırsın ya da arkadaşın "hangi kitap?" der ve kitap kaybolur. C++'ta new ile oluşturduğun her nesne bu kitap gibidir — birisi delete çağırmadığı sürece bellekte asılı kalır. Kimse çağırmazsa: bellek sızıntısı (memory leak). Yanlış zamanda çağırırsa: dangling pointer ve program çöker.

İşte smart pointer'lar tam olarak bu sorunu çözmek için var. C++11 ile gelen unique_ptr, shared_ptr ve weak_ptr, bellek yönetimini otomatikleştiren, hataları derleme zamanında yakalayan ve kodunuzu güvenli hale getiren araçlardır. Modern C++ yazıyorsanız ham pointer (raw pointer) kullanmanız gereken yer neredeyse kalmamıştır.

Bu yazıda üç smart pointer türünü derinlemesine inceleyeceğiz: her birinin sahiplik modeli, dahili çalışma mekanizması, gerçek dünya kullanım alanları ve sizi dertten kurtaracak best practice'ler.

Raw Pointer'ların Sorunu

Smart pointer'ları anlamak için önce raw pointer'ların neden yetersiz kaldığını görmemiz lazım. Aşağıdaki kodu inceleyin:

#include <iostream>
#include <string>

class DatabaseConnection {
public:
    DatabaseConnection(const std::string& host) : host_(host) {
        std::cout << "Veritabanına bağlanıldı: " << host_ << std::endl;
    }

    ~DatabaseConnection() {
        std::cout << "Bağlantı kapatıldı: " << host_ << std::endl;
    }

    void query(const std::string& sql) {
        std::cout << "Sorgu çalıştırılıyor: " << sql << std::endl;
    }

private:
    std::string host_;
};

void processData() {
    DatabaseConnection* conn = new DatabaseConnection("localhost:5432");

    conn->query("SELECT * FROM users");

    // Burada bir exception fırlarsa ne olur?
    // delete conn; asla çağrılmaz → bellek sızıntısı!

    if (true) { // Bir koşul
        return; // Erken dönüş → delete çağrılmadı!
    }

    delete conn; // Bu satıra asla ulaşılmaz
}

int main() {
    processData();
    std::cout << "Program bitti" << std::endl;
    return 0;
}

Bu kodda üç kritik sorun var:

  1. Erken return: Fonksiyon ortasında return çağrıldığında delete satırına asla ulaşılmaz.

  2. Exception: query() bir istisna fırlatırsa yine delete çağrılmaz.

  3. Karmaşık akış: Kod büyüdükçe her yolda delete çağırmayı hatırlamak imkansızlaşır.

Smart pointer'lar bu sorunları RAII (Resource Acquisition Is Initialization) prensibiyle çözer. Nesne scope'dan çıktığında (yani yığından — stack'ten — temizlendiğinde) otomatik olarak sahip olduğu kaynağı serbest bırakır. Artık delete yazmayı hatırlamak zorunda değilsiniz.

unique_ptr: Tek Sahiplik, Sıfır Maliyet

unique_ptr, bir kaynağın tek bir sahibi olduğunu garanti eden smart pointer'dır. Sahiplik devredilebilir ama paylaşılamaz. Bir unique_ptr kopyalanamaz, sadece taşınabilir (move).

Bunu şöyle düşünün: evinizin anahtarının tek bir kopyası var. Anahtarı birine verirseniz artık sizde kalmaz.

Temel Kullanım

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Sensor {
public:
    Sensor(const std::string& name, double value)
        : name_(name), value_(value) {
        std::cout << "Sensor oluşturuldu: " << name_ << std::endl;
    }

    ~Sensor() {
        std::cout << "Sensor yok edildi: " << name_ << std::endl;
    }

    void read() const {
        std::cout << name_ << " okuması: " << value_ << std::endl;
    }

    void setValue(double v) { value_ = v; }
    const std::string& getName() const { return name_; }

private:
    std::string name_;
    double value_;
};

int main() {
    // make_unique ile oluştur (C++14, önerilen yol)
    auto tempSensor = std::make_unique<Sensor>("Sıcaklık", 23.5);
    tempSensor->read(); // Sensor okuması: 23.5

    // Sahiplik devri (move semantics)
    auto movedSensor = std::move(tempSensor);
    // tempSensor artık nullptr!

    if (!tempSensor) {
        std::cout << "tempSensor artık boş (nullptr)" << std::endl;
    }

    movedSensor->read(); // Hâlâ çalışır

    // unique_ptr'ları vector'de sakla
    std::vector<std::unique_ptr<Sensor>> sensors;
    sensors.push_back(std::make_unique<Sensor>("Nem", 65.0));
    sensors.push_back(std::make_unique<Sensor>("Basınç", 1013.25));
    sensors.push_back(std::move(movedSensor)); // Taşıyarak ekle

    std::cout << "\nTüm sensorler:" << std::endl;
    for (const auto& s : sensors) {
        s->read();
    }

    std::cout << "\nScope sonu — tüm sensorler otomatik silinir:" << std::endl;
    return 0;
}

Çıktı:

Sensor oluşturuldu: Sıcaklık
Sıcaklık okuması: 23.5
tempSensor artık boş (nullptr)
Sıcaklık okuması: 23.5
Sensor oluşturuldu: Nem
Sensor oluşturuldu: Basınç

Tüm sensorler:
Nem okuması: 65
Basınç okuması: 1013.25
Sıcaklık okuması: 23.5

Scope sonu — tüm sensorler otomatik silinir:
Sensor yok edildi: Nem
Sensor yok edildi: Basınç
Sensor yok edildi: Sıcaklık

unique_ptr'ın Sıfır Maliyeti

unique_ptr'ın en güzel tarafı: çalışma zamanı maliyeti sıfırdır. Derleyici, unique_ptr<T> ile T* arasında aynı makine kodunu üretir. Ekstra bellek tüketmez, ekstra indirection yapmaz. Bu yüzden varsayılan tercihiniz her zaman unique_ptr olmalıdır.

Custom Deleter

Bazen kaynağı serbest bırakma şekliniz standart delete değildir. Dosya tanıtıcıları (file handle), C kütüphanesi kaynakları veya özel havuzlar için custom deleter kullanabilirsiniz:

#include <iostream>
#include <memory>
#include <cstdio>

int main() {
    // FILE* için custom deleter
    auto fileDeleter = [](FILE* fp) {
        if (fp) {
            std::cout << "Dosya kapatılıyor..." << std::endl;
            fclose(fp);
        }
    };

    {
        std::unique_ptr<FILE, decltype(fileDeleter)> file(
            fopen("test.txt", "w"), fileDeleter
        );

        if (file) {
            fputs("Smart pointer ile dosya yonetimi!\n", file.get());
            std::cout << "Dosyaya yazıldı." << std::endl;
        }
    } // Scope bitti → fileDeleter otomatik çağrılır

    std::cout << "Dosya güvenle kapatıldı." << std::endl;
    return 0;
}

Bu pattern özellikle C kütüphaneleriyle çalışırken hayat kurtarır. fopen / fclose, malloc / free, veya herhangi bir acquire / release çifti için kullanabilirsiniz.

shared_ptr: Paylaşılan Sahiplik

Bazı senaryolarda bir kaynağa birden fazla yerden erişmeniz gerekir ve hangisinin en son işini bitireceği belli değildir. İşte burada shared_ptr devreye girer.

shared_ptr, bir referans sayacı (reference count) tutar. Her kopya sayacı 1 artırır, her yıkım 1 azaltır. Sayaç sıfıra düştüğünde kaynak otomatik serbest bırakılır. Bunu bir otel odası gibi düşünün: son kişi çıkınca ışıklar kapanır.

Temel Kullanım

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Logger {
public:
    Logger(const std::string& name) : name_(name) {
        std::cout << "Logger oluşturuldu: " << name_ << std::endl;
    }

    ~Logger() {
        std::cout << "Logger yok edildi: " << name_ << std::endl;
    }

    void log(const std::string& message) const {
        std::cout << "[" << name_ << "] " << message << std::endl;
    }

private:
    std::string name_;
};

class Service {
public:
    Service(const std::string& name, std::shared_ptr<Logger> logger)
        : name_(name), logger_(logger) {
        logger_->log(name_ + " servisi başlatıldı");
    }

    ~Service() {
        logger_->log(name_ + " servisi durduruluyor");
    }

    void doWork() {
        logger_->log(name_ + " çalışıyor...");
    }

private:
    std::string name_;
    std::shared_ptr<Logger> logger_;
};

int main() {
    auto logger = std::make_shared<Logger>("AppLogger");
    std::cout << "Ref count: " << logger.use_count() << std::endl; // 1

    {
        auto authService = std::make_shared<Service>("Auth", logger);
        std::cout << "Ref count: " << logger.use_count() << std::endl; // 2

        auto userService = std::make_shared<Service>("User", logger);
        std::cout << "Ref count: " << logger.use_count() << std::endl; // 3

        authService->doWork();
        userService->doWork();

        std::cout << "\nİç scope bitiyor..." << std::endl;
    } // authService ve userService yok edilir, ref count 1'e düşer

    std::cout << "Ref count: " << logger.use_count() << std::endl; // 1
    logger->log("Logger hâlâ hayatta!");

    std::cout << "\nMain bitiyor..." << std::endl;
    return 0;
}

Çıktı:

Logger oluşturuldu: AppLogger
Ref count: 1
[AppLogger] Auth servisi başlatıldı
Ref count: 2
[AppLogger] User servisi başlatıldı
Ref count: 3
[AppLogger] Auth çalışıyor...
[AppLogger] User çalışıyor...

İç scope bitiyor...
[AppLogger] User servisi durduruluyor
[AppLogger] Auth servisi durduruluyor
Ref count: 1
[AppLogger] Logger hâlâ hayatta!

Main bitiyor...
Logger yok edildi: AppLogger

shared_ptr'ın Maliyeti

shared_ptr, unique_ptr'ın aksine bedava değildir:

  • Kontrol bloğu (control block): Referans sayacı ve weak count için heap'te ekstra bir blok ayırır. make_shared kullanırsanız nesne ve kontrol bloğu tek bir allocation'da birleştirilir — bu önemli bir optimizasyondur.

  • Atomik operasyonlar: Referans sayacı artırma/azaltma atomiktir (thread-safe). Bu, tek iş parçacıklı kodda bile küçük bir maliyet getirir.

  • Bellek: Tipik olarak bir shared_ptr 2 pointer boyutundadır (nesne + kontrol bloğu), raw pointer'ın 2 katı.

Bu maliyetler çoğu uygulamada ihmal edilebilir düzeydedir. Ama yüksek performans gereken tight loop'larda farkında olmanız gerekir.

weak_ptr: Döngüsel Referansları Kırma

shared_ptr harika bir araçtır, ama bir tuzağı vardır: döngüsel referans (circular reference). İki nesne birbirini shared_ptr ile tutarsa referans sayaçları asla sıfıra düşmez ve bellek sızıntısı oluşur.

weak_ptr, bir shared_ptr'ın gösterdiği nesneyi sahiplenmeden gözlemleyen bir pointer'dır. Referans sayacını artırmaz. Nesneye erişmek istediğinizde lock() çağırarak geçici bir shared_ptr elde edersiniz — nesne hâlâ hayattaysa.

Döngüsel Referans Problemi ve Çözümü

#include <iostream>
#include <memory>
#include <string>

// YANLIŞ: Döngüsel referans — bellek sızıntısı!
class BadNode {
public:
    std::string name;
    std::shared_ptr<BadNode> next; // Güçlü referans

    BadNode(const std::string& n) : name(n) {
        std::cout << "BadNode oluşturuldu: " << name << std::endl;
    }
    ~BadNode() {
        std::cout << "BadNode yok edildi: " << name << std::endl;
    }
};

// DOĞRU: weak_ptr ile döngü kırılır
class GoodNode {
public:
    std::string name;
    std::shared_ptr<GoodNode> next;
    std::weak_ptr<GoodNode> prev; // Zayıf referans — döngüyü kırar

    GoodNode(const std::string& n) : name(n) {
        std::cout << "GoodNode oluşturuldu: " << name << std::endl;
    }
    ~GoodNode() {
        std::cout << "GoodNode yok edildi: " << name << std::endl;
    }

    void showPrev() {
        // lock() ile güvenli erişim
        if (auto p = prev.lock()) {
            std::cout << name << "'in önceki düğümü: " << p->name << std::endl;
        } else {
            std::cout << name << "'in önceki düğümü artık yok!" << std::endl;
        }
    }
};

int main() {
    std::cout << "=== YANLIŞ: Döngüsel referans ===" << std::endl;
    {
        auto a = std::make_shared<BadNode>("A");
        auto b = std::make_shared<BadNode>("B");
        a->next = b;
        b->next = a; // Döngü! A → B → A → B → ...
        std::cout << "Scope bitiyor ama destructor ÇAĞRILMAYACAK!" << std::endl;
    }
    // BadNode yok edildi mesajı GÖRÜNMEZ — bellek sızıntısı!

    std::cout << "\n=== DOĞRU: weak_ptr ile ===" << std::endl;
    {
        auto first = std::make_shared<GoodNode>("First");
        auto second = std::make_shared<GoodNode>("Second");
        auto third = std::make_shared<GoodNode>("Third");

        // İleri yön: shared_ptr (güçlü sahiplik)
        first->next = second;
        second->next = third;

        // Geri yön: weak_ptr (zayıf gözlem)
        second->prev = first;
        third->prev = second;

        third->showPrev(); // Second
        second->showPrev(); // First

        std::cout << "Scope bitiyor — tüm düğümler temizlenecek:" << std::endl;
    }

    return 0;
}

Çıktı:

=== YANLIŞ: Döngüsel referans ===
BadNode oluşturuldu: A
BadNode oluşturuldu: B
Scope bitiyor ama destructor ÇAĞRILMAYACAK!

=== DOĞRU: weak_ptr ile ===
GoodNode oluşturuldu: First
GoodNode oluşturuldu: Second
GoodNode oluşturuldu: Third
Third'in önceki düğümü: Second
Second'in önceki düğümü: First
Scope bitiyor — tüm düğümler temizlenecek:
GoodNode yok edildi: First
GoodNode yok edildi: Second
GoodNode yok edildi: Third

Dikkat edin: BadNode destructor'ları asla çağrılmadı. Bu, tipik bir döngüsel referans bellek sızıntısıdır. GoodNode'da ise prev alanında weak_ptr kullandığımız için geri yöndeki referans sayacı artmaz ve zincir doğru şekilde temizlenir.

weak_ptr'ın Diğer Kullanım Alanları

weak_ptr sadece döngüsel referans kırmak için değildir. Bir önbellek (cache) sistemi düşünün: nesneyi önbellekte tutuyorsunuz ama önbelleğin nesneyi hayatta tutmasını istemiyorsunuz. Nesneye ihtiyaç duyan biri varsa (shared_ptr tutan) hayatta kalır, yoksa silinir. lock() ile kontrol edip gerekirse yeniden oluşturabilirsiniz.

Yaygın Hatalar ve Tuzaklar

1. shared_ptr'ı Aynı Raw Pointer'dan İki Kez Oluşturmak

// YANLIŞ — iki ayrı kontrol bloğu, çift delete!
int* raw = new int(42);
std::shared_ptr<int> sp1(raw);
std::shared_ptr<int> sp2(raw); // BÜYÜK HATA!
// sp1 ve sp2 farklı kontrol blokları → ikisi de delete çağırır → CRASH

Çözüm: Bir raw pointer'ı asla iki farklı shared_ptr'a vermeyin. make_shared kullanın veya bir shared_ptr'dan kopyalayın.

2. this Pointer'ını shared_ptr'a Sarmak

// YANLIŞ
class Widget {
public:
    std::shared_ptr<Widget> getShared() {
        return std::shared_ptr<Widget>(this); // TEHLİKE!
    }
};

Çözüm: std::enable_shared_from_this kullanın:

class Widget : public std::enable_shared_from_this<Widget> {
public:
    std::shared_ptr<Widget> getShared() {
        return shared_from_this(); // Güvenli
    }
};

// Kullanım — nesne shared_ptr ile yönetilmeli
auto w = std::make_shared<Widget>();
auto w2 = w->getShared(); // Aynı kontrol bloğunu paylaşır

3. unique_ptr'ı Kopyalamaya Çalışmak

auto ptr = std::make_unique<int>(42);
// auto copy = ptr; // DERLEME HATASI! unique_ptr kopyalanamaz

auto moved = std::move(ptr); // Doğru: sahiplik devri

4. make_shared / make_unique Kullanmamak

// Zayıf yol — iki ayrı allocation ve exception-safety riski
std::shared_ptr<Widget> sp(new Widget());

// Güçlü yol — tek allocation, exception-safe
auto sp = std::make_shared<Widget>();
auto up = std::make_unique<Widget>(); // C++14

make_shared, nesne ve kontrol bloğunu tek bir bellek tahsisinde birleştirir. Bu hem daha hızlıdır hem de fonksiyon argümanlarında exception safety sağlar.

5. Döngü İçinde Gereksiz shared_ptr Kopyası

void process(const std::vector<std::shared_ptr<Data>>& items) {
    for (const auto& item : items) { // const referans — kopya yok, doğru
        item->process();
    }

    // YANLIŞ: her iterasyonda ref count artırılıp azaltılır
    for (auto item : items) { // Kopya! Gereksiz atomik işlem
        item->process();
    }
}

Sahipliği paylaşmanız gerekmiyorsa const auto& ile referans alın. Fonksiyon parametrelerinde de aynı kural geçerlidir: nesneyi sadece kullanacaksanız const shared_ptr<T>& veya daha iyisi raw const T& / const T* alın.

Best Practices: Profesyonelin Kuralları

1. Varsayılan Tercihiniz unique_ptr Olsun

Çoğu durumda tek sahiplik yeterlidir. Önce unique_ptr ile başlayın, gerçekten paylaşılan sahiplik gerekirse shared_ptr'a geçin. unique_ptr'dan shared_ptr'a dönüşüm kolaydır:

auto up = std::make_unique<Sensor>("Sıcaklık", 22.0);
std::shared_ptr<Sensor> sp = std::move(up); // Sorunsuz

Tersi mümkün değildir. Yani shared_ptr'dan unique_ptr'a geri dönemezsiniz.

2. Fonksiyon Parametrelerinde Doğru Tip

AmaçParametre Tipi
Sadece kullanmakconst T& veya T*
Sahipliği devretmekunique_ptr<T> (by value)
Sahipliği paylaşmakshared_ptr<T> (by value)
Sahipliği belki paylaşmakconst shared_ptr<T>&

3. Diziler İçin unique_ptr

C++14 ve sonrasında unique_ptr dizi desteği sunar:

auto arr = std::make_unique<int[]>(100); // 100 elemanlı int dizisi
arr[0] = 42;
arr[99] = 7;
// Scope bitince delete[] otomatik çağrılır

Ancak çoğu durumda std::vector veya std::array tercih edilmelidir. Dizi smart pointer'ı daha çok C API'leriyle çalışırken veya sabit boyutlu tamponlar gerektiğinde kullanışlıdır.

4. Thread Safety Hakkında Gerçekler

shared_ptr'ın referans sayacı atomiktir — yani sayaç artırma ve azaltma thread-safe'dir. Ama bu, shared_ptr'ın gösterdiği nesnenin thread-safe olduğu anlamına gelmez! Aynı nesneye birden fazla thread'den yazıyorsanız mutex gibi senkronizasyon mekanizmaları kullanmanız gerekir.

Ayrıca aynı shared_ptr instance'ına (nesneye değil, pointer'ın kendisine) birden fazla thread'den yazma/okuma yapmak da güvenli değildir. Farklı thread'ler kendi shared_ptr kopyalarını tutuyorsa sorun yoktur.

5. Performans Karşılaştırması

ÖzellikRaw Pointerunique_ptrshared_ptr
Boyut8 byte8 byte16 byte
Heap tahsisManuelTekTek (make_shared) veya İki
KopyalamaEvetHayır (taşınır)Evet (atomik)
Çalışma zamanı maliyetiYokYokDüşük (atomik sayaç)
Thread-safe sayaçN/AN/AEvet

Gerçek Dünya Örneği: Plugin Sistemi

Birden fazla kavramı birleştiren bir örnek görelim. Bir uygulama için basit bir plugin sistemi tasarlayalım:

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>

// Plugin arayüzü
class IPlugin {
public:
    virtual ~IPlugin() = default;
    virtual std::string getName() const = 0;
    virtual void execute() = 0;
};

// Örnek pluginler
class JsonPlugin : public IPlugin {
public:
    std::string getName() const override { return "JsonPlugin"; }
    void execute() override {
        std::cout << "JSON verisi işleniyor..." << std::endl;
    }
};

class CsvPlugin : public IPlugin {
public:
    std::string getName() const override { return "CsvPlugin"; }
    void execute() override {
        std::cout << "CSV verisi dışa aktarılıyor..." << std::endl;
    }
};

// Plugin yöneticisi — pluginlerin tek sahibi
class PluginManager {
public:
    // Sahipliği devralır (unique_ptr by value)
    void registerPlugin(std::unique_ptr<IPlugin> plugin) {
        std::cout << "Plugin kaydedildi: " << plugin->getName() << std::endl;
        std::string name = plugin->getName();
        plugins_[name] = std::move(plugin);
    }

    // Ham pointer döner — sahiplik devretmez, sadece erişim sağlar
    IPlugin* getPlugin(const std::string& name) {
        auto it = plugins_.find(name);
        if (it != plugins_.end()) {
            return it->second.get();
        }
        return nullptr;
    }

    void executeAll() {
        std::cout << "\nTüm pluginler çalıştırılıyor:" << std::endl;
        for (const auto& [name, plugin] : plugins_) {
            std::cout << "  → ";
            plugin->execute();
        }
    }

    size_t count() const { return plugins_.size(); }

private:
    std::unordered_map<std::string, std::unique_ptr<IPlugin>> plugins_;
};

// Loglama servisi — birden fazla bileşen tarafından paylaşılır
class LogService {
public:
    LogService() { std::cout << "LogService başlatıldı" << std::endl; }
    ~LogService() { std::cout << "LogService kapatıldı" << std::endl; }

    void info(const std::string& msg) {
        std::cout << "[INFO] " << msg << std::endl;
    }
};

// Uygulama — her şeyi bir araya getirir
class Application {
public:
    Application()
        : logger_(std::make_shared<LogService>()),
          pluginManager_(std::make_unique<PluginManager>()) {
        logger_->info("Uygulama başlatılıyor...");
    }

    void init() {
        // Pluginleri oluştur ve sahipliği manager'a devret
        pluginManager_->registerPlugin(std::make_unique<JsonPlugin>());
        pluginManager_->registerPlugin(std::make_unique<CsvPlugin>());

        logger_->info("Toplam " + std::to_string(pluginManager_->count()) + " plugin yüklendi");
    }

    void run() {
        logger_->info("Uygulama çalışıyor");
        pluginManager_->executeAll();

        // Belirli bir plugin'e erişim (sahiplik almadan)
        if (auto* json = pluginManager_->getPlugin("JsonPlugin")) {
            logger_->info("JsonPlugin tekrar çalıştırılıyor...");
            json->execute();
        }
    }

    // Logger'ı dışarıyla paylaş
    std::shared_ptr<LogService> getLogger() const { return logger_; }

private:
    std::shared_ptr<LogService> logger_;         // Paylaşılan sahiplik
    std::unique_ptr<PluginManager> pluginManager_; // Tek sahiplik
};

int main() {
    {
        Application app;
        app.init();
        app.run();

        // Logger'ı başka bir yerde de kullanabiliriz
        auto sharedLogger = app.getLogger();
        sharedLogger->info("Main'den loglama");

        std::cout << "\nUygulama scope'u bitiyor..." << std::endl;
    }

    std::cout << "Temizlik tamamlandı." << std::endl;
    return 0;
}

Bu örnekte üç smart pointer türünün birlikte çalıştığını görüyorsunuz:

  • `unique_ptr`: PluginManager pluginlerin tek sahibidir. Pluginler dışarıdan oluşturulup std::move ile manager'a devredilir.

  • `shared_ptr`: LogService birden fazla bileşen tarafından paylaşılır. Application ana sahiptir ama getLogger() ile dışarıya da paylaşabilir.

  • Raw pointer (get()): getPlugin() sahiplik devretmez, sadece geçici erişim sağlar. Çağıran taraf nesneyi tutmaz.

Ne Zaman Hangisini Kullanmalı?

Karar ağacı basittir:

  1. Sahiplik gerekmiyor mu? → Raw pointer veya referans kullanın.

  2. Tek sahip yeterli mi?unique_ptr kullanın. (Vakaların %90'ı buraya düşer.)

  3. Birden fazla sahip gerekli mi?shared_ptr kullanın.

  4. Döngüsel referans veya gözlemci mi?weak_ptr kullanın.

  5. Emin değil misiniz?unique_ptr ile başlayın, gerekirse yükseltin.

Sonuç

  • `unique_ptr` sıfır maliyetle tek sahiplik sağlar — varsayılan tercihiniz budur.

  • `shared_ptr` referans sayacıyla paylaşılan sahiplik sunar — gerçekten paylaşım gerektiğinde kullanın.

  • `weak_ptr` döngüsel referansları kırar ve gözlemci pattern'ı sağlar — shared_ptr ile birlikte kullanılır.

  • make_unique ve make_shared her zaman tercih edilmelidir — daha güvenli, daha performanslı.

  • Fonksiyon parametrelerinde sahiplik niyetinizi tip ile ifade edin — bu kodu okuyan herkes için bir belgelendirmedir.

  • Raw new ve delete yazmayı bırakın. Modern C++'ta neredeyse hiçbir zaman gerekmez.

Smart pointer'lar, C++'ın en güçlü özelliklerinden biridir. Onları doğru kullanmak, bellek sızıntılarını, dangling pointer'ları ve double-free hatalarını tarihe gömmenizi sağlar. Kodunuz hem daha güvenli hem de niyetinizi daha açık ifade eden bir hale gelir. Her new gördüğünüzde kendinize sorun: "Bu neden bir smart pointer değil?"

Paylaş:
Son güncelleme: Jun 04, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

İlgili Yazılar