Menu
Coddy logo textTech

Bir Kalıp Olarak RAII

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 102 / 104.

RAII (Resource Acquisition Is Initialization) sadece bir C++ deyimi olmaktan daha fazlasıdır; kaynak yönetimini nesne ömrüne bağlayan güçlü bir tasarım desenidir. RAII'yi akıllı işaretçilerle (smart pointers) zaten gördünüz, ancak bu desen herhangi bir kaynağa uygulanabilir: dosya tanıtıcıları, ağ bağlantıları, mutex'ler veya veritabanı işlemleri.

Temel fikir basittir: kaynakları yapıcıda (constructor) edinin, yıkıcıda (destructor) serbest bırakın. C++, nesneler kapsam dışına çıktığında yıkıcıların çalışmasını garanti ettiği için, temizleme işlemi -istisnalar (exceptions) oluşsa bile- otomatik olarak gerçekleşir:

#include <iostream>
#include <fstream>

class FileGuard {
    std::ofstream file;
public:
    FileGuard(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            std::cout << "Failed to open file\n";
        }
    }
    
    void write(const std::string& text) {
        if (file.is_open()) file << text;
    }
    
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed automatically\n";
        }
    }
};

int main() {
    {
        FileGuard guard("output.txt");
        guard.write("Hello RAII");
    }  // Yıkıcı burada çağrıldı - dosya kapatıldı
    
    std::cout << "After scope\n";
}

RAII, çok iş parçacıklı kodlarda kilitleri (locks) yönetirken öne çıkar. Standart kütüphanenin std::lock_guard bileşeni bu deseni takip eder; oluşturulma anında bir mutex edinir ve yok edildiğinde onu serbest bırakarak, unutulan kilit açma işlemlerinden kaynaklanan kilitlenmeleri (deadlocks) önler.

RAII sınıflarını uygularken, kaynak çoğaltma veya çift serbest bırakma (double-release) sorunlarını önlemek için kopyalama/taşıma işlemlerini (Beşli Kural - Rule of Five) ya silmeyi ya da düzgün bir şekilde uygulamayı unutmayın. RAII, hataya açık manuel kaynak yönetimini güvenli ve otomatik temizlemeye dönüştürür.

challenge icon

Görev

Kolay

Veritabanı bağlantılarını güvenli bir şekilde yönetmek için RAII kullanarak bir Bağlantı Havuzu Yöneticisi (Connection Pool Manager) oluşturalım. Gerçek uygulamalarda, veritabanı bağlantıları, uygun şekilde alınması ve serbest bırakılması gereken maliyetli kaynaklardır. İstisnalar oluşsa veya kod yolları karmaşıklaşsa bile bağlantıların her zaman havuza geri döndürülmesini garanti eden bir RAII sarmalayıcısı oluşturacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • ConnectionPool.h: Sınırlı sayıda bağlantıyı yöneten basit bir bağlantı havuzu oluşturun.

    ConnectionPool sınıfınız kaç bağlantının mevcut olduğunu takip etmelidir (yapıcıya iletilen bir kapasite ile başlayın). Şunları uygulayın:

    • acquire() — eğer bir bağlantı mevcutsa, sayıyı azaltın ve "Connection acquired (X available)" yazdırın (burada X kalan sayıdır); başarılıysa true, bağlantı yoksa false döndürün
    • release() — mevcut sayıyı artırın ve "Connection released (X available)" yazdırın
    • available() — mevcut bağlantı sayısını döndürür
  • ConnectionGuard.h: Tek bir bağlantıyı güvenli bir şekilde yöneten RAII sarmalayıcısını oluşturun.

    ConnectionGuard sınıfınız RAII desenini somutlaştırır. Şunları yapmalıdır:

    • Yapıcısında bir ConnectionPool referansı almalı ve bir bağlantı edinmeye çalışmalıdır
    • Edinmenin başarılı olup olmadığını saklamalıdır
    • Guard'ın geçerli bir bağlantıya sahip olup olmadığını kontrol etmek için bir isConnected() yöntemi sağlamalıdır
    • Yıkıcıda (destructor) bağlantıyı otomatik olarak havuza geri bırakmalıdır (yalnızca bir bağlantı edinilmişse)
    • Kaynak çoğaltılmasını önlemek için kopyalama yapıcısını (copy constructor) ve kopyalama atamasını (copy assignment) silmelidir (Beşli Kural - Rule of Five değerlendirmesi)

    Yıkıcı çalıştığında, eğer bir bağlantı tutuluyorsa, havuz üzerinde release çağırmadan önce "Guard releasing connection" yazdırın.

  • main.cpp: Kapsamlar (scopes) aracılığıyla RAII'nin otomatik temizliğini gösterin.

    İki girdi okuyun:

    1. Havuz kapasitesi (tam sayı)
    2. İstenecek bağlantı sayısı (tam sayı)

    Verilen kapasite ile bir ConnectionPool oluşturun. Ardından, iç içe geçmiş bir kapsam (süslü parantez kullanarak) içinde, bir vektörde saklanan istenen sayıda ConnectionGuard nesnesi oluşturun. Her guard için başarıyla bağlanıp bağlanmadığını yazdırın:

    • Bağlıysa: "Guard N: Connected"
    • Bağlı değilse: "Guard N: Failed to connect"

    (burada N, 1'den başlar)

    Kapsam sona erdikten sonra (guard'lar yok edildikten sonra), havuzun son durumunu gösteren "After scope: X connections available" yazdırın.

Örneğin, 2 ve 3 girdileriyle:

Connection acquired (1 available)
Guard 1: Connected
Connection acquired (0 available)
Guard 2: Connected
Guard 3: Failed to connect
Guard releasing connection
Connection released (1 available)
Guard releasing connection
Connection released (2 available)
After scope: 2 connections available

3 ve 2 girdileriyle:

Connection acquired (2 available)
Guard 1: Connected
Connection acquired (1 available)
Guard 2: Connected
Guard releasing connection
Connection released (2 available)
Guard releasing connection
Connection released (3 available)
After scope: 3 connections available

Guard'lar kapsam dışına çıktığında bağlantıların nasıl otomatik olarak serbest bırakıldığına dikkat edin; ana kodunuzda asla açıkça release çağırmazsınız. Yıkıcılar, oluşturulma sırasının tersine çalışır (en son oluşturulan guard ilk önce yok edilir) ve edinilen her bağlantının geri döndürülmesi garanti edilir. RAII'nin gücü budur: kapsamdan nasıl çıkılırsa çıkılsın, kaynak temizliği otomatik ve güvenilir bir şekilde gerçekleşir.

Kopya kağıdı

RAII (Resource Acquisition Is Initialization - Kaynak Edinimi Başlatmadır), kaynak yönetimini nesne ömrüne bağlayan bir tasarım desenidir. Kaynaklar kurucuda (constructor) edinilir ve yıkıcıda (destructor) serbest bırakılır.

C++, nesneler kapsam dışına çıktığında yıkıcıların çalışmasını garanti ettiğinden, temizleme işlemi otomatik olarak gerçekleşir; istisnalar (exceptions) oluşsa bile:

class FileGuard {
    std::ofstream file;
public:
    FileGuard(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            std::cout << "Failed to open file\n";
        }
    }
    
    void write(const std::string& text) {
        if (file.is_open()) file << text;
    }
    
    ~FileGuard() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed automatically\n";
        }
    }
};

int main() {
    {
        FileGuard guard("output.txt");
        guard.write("Hello RAII");
    }  // Yıkıcı burada çağrılır - dosya otomatik olarak kapatılır
    
    std::cout << "After scope\n";
}

RAII, özellikle çok iş parçacıklı (multithreaded) kodlarda kilitleri yönetmek için kullanışlıdır. Standart kütüphanenin std::lock_guard sınıfı, oluşturulma sırasında bir mutex edinir ve yok edilme sırasında onu serbest bırakarak, unutulan kilit açma işlemlerinden kaynaklanan kilitlenmeleri (deadlock) önler.

RAII sınıflarını uygularken, kaynak çoğaltma veya çift serbest bırakma sorunlarını önlemek için kopyalama/taşıma işlemlerini (Beşli Kuralı - Rule of Five) silin veya düzgün bir şekilde uygulayın.

Kendin dene

#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"

using namespace std;

int main() {
    // Girdileri oku
    int capacity;
    int numConnections;
    cin >> capacity;
    cin >> numConnections;
    
    // TODO: Verilen kapasite ile bir ConnectionPool oluşturun
    
    // TODO: Süslü parantez kullanarak iç içe bir kapsam (scope) oluşturun
    {
        // TODO: ConnectionGuard nesnelerini saklamak için bir vector oluşturun
        // İpucu: ConnectionGuard silinmiş bir kopyalama yapıcısına (copy constructor) sahip olduğu için
        // işaretçiler (pointers) veya akıllı işaretçiler (smart pointers) kullanmanız gerekecek
        
        // TODO: numConnections kadar guard oluşturmak için bir döngü kurun
        // Her bir guard için şunlardan birini yazdırın:
        // "Guard N: Connected" veya "Guard N: Failed to connect"
        // burada N değeri 1'den başlar
        
    }
    // Kapsam sona erdiğinde guard'lar burada yok edilir
    
    // TODO: "After scope: X connections available" yazdırın
    
    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler