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.
Görev
KolayVeritabanı 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.ConnectionPoolsı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ıysatrue, bağlantı yoksafalsedöndürünrelease()— mevcut sayıyı artırın ve"Connection released (X available)"yazdırınavailable()— 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.ConnectionGuardsınıfınız RAII desenini somutlaştırır. Şunları yapmalıdır:- Yapıcısında bir
ConnectionPoolreferansı 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.- Yapıcısında bir
main.cpp: Kapsamlar (scopes) aracılığıyla RAII'nin otomatik temizliğini gösterin.İki girdi okuyun:
- Havuz kapasitesi (tam sayı)
- İstenecek bağlantı sayısı (tam sayı)
Verilen kapasite ile bir
ConnectionPoololuşturun. Ardından, iç içe geçmiş bir kapsam (süslü parantez kullanarak) içinde, bir vektörde saklanan istenen sayıdaConnectionGuardnesnesi 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 available3 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 availableGuard'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;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı11İleri Düzey OOP Kavramları
Kompozisyon ve Kalıtım KarşılaştırmasıCRTP ile Mixin YapılarıPimpl İdiyomuTür Silme (Type Erasure)Enum Sınıfları ve Güçlü TiplendirmeOOP'de İstisna YönetimiÖzel İstisna Hiyerarşileri14Tasarım Kalıpları Bölüm 2
Komut KalıbıAdaptör KalıbıDekoratör KalıbıŞablon Metot KalıbıDurum KalıbıKompozit KalıbıBir Kalıp Olarak RAII3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner