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), yalnızca bir C++ deyimi değildir; kaynak yönetimini nesnenin yaşam süresine bağlayan güçlü bir tasarım kalıbıdır. RAII'yi akıllı işaretçilerle zaten gördün, ancak bu kalıp her türlü kaynak için geçerlidir: dosya tanıtıcıları, ağ connections, mutex'ler veya veritabanı işlemleri.
Temel fikir basittir: kaynakları Constructor'da acquire edin, kaynakları yıkıcıda serbest bırakın. 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 oluştuğunda bile:
#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");
} // Destructor burada çağrıldı - dosya kapatıldı
std::cout << "After scope\n";
}RAII, çok iş parçacıklı kodda kilitleri yönetirken öne çıkar. Standart kitaplığın std::lock_guard sınıfı bu deseni izler: oluşturulurken bir mutex acquire eder ve yok edilirken serbest bırakır; böylece unutulan kilit açma işlemlerinden kaynaklanan deadlock'ları önler.
RAII sınıflarını uygularken, kaynakların çoğaltılması veya iki kez serbest bırakılması sorunlarını önlemek için either delete etmeyi ya da copy/move işlemlerini doğru şekilde uygulamayı (Rule of Five) unutma. RAII, hataya açık manuel kaynak yönetimini güvenli ve otomatik temizlemeye dönüştürür.
Görev
KolayRAII kullanarak veritabanı bağlantılarını güvenli bir şekilde yönetmek için bir Connection Pool Manager oluşturalım. Gerçek uygulamalarda veritabanı bağlantıları, düzgün şekilde acquire edilmesi ve release edilmesi gereken pahalı kaynaklardır. İstisnalar oluşsa veya kod yolları karmaşık hâle gelse bile bağlantıların pool'a her zaman geri döndürülmesini garanti eden bir RAII sarmalayıcısı oluşturacaksın.
Kodunu üç dosya arasında organize edeceksin:
ConnectionPool.h: Sınırlı sayıda bağlantıyı yöneten basit bir connection pool Create et.ConnectionPoolsınıfın kaç bağlantının available olduğunu izlemelidir (Constructor'a geçirilen bir capacity ile başla). Şunları uygula:acquire()— bir bağlantı available ise sayıyı azalt veConnection acquired (X available)yazdır; başarılıysatrue, hiç bağlantı available değilsefalsedöndürrelease()— available sayısını artır veConnection released (X available)yazdıravailable()— mevcut available bağlantıların 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ştur.ConnectionGuardsınıfın RAII desenini temsil eder. Şunları yapmalıdır:- Constructor'ında bir
ConnectionPoolreferansı almalı ve bir bağlantı acquire etmeye çalışmalı - acquisition işleminin başarılı olup olmadığını saklamalı
- Guard'ın geçerli bir bağlantı tutup tutmadığını kontrol etmek için bir
isConnected()metodu sağlamalı - Bağlantıyı (yalnızca biri acquire edildiyse) Destructor'da otomatik olarak pool'a release etmeli
- Kaynak çoğaltılmasını önlemek için copy constructor ve copy assignment'ı Delete etmeli (Rule of Five değerlendirmesi)
Destructor çalıştığında, bir bağlantı tutuluyorsa pool üzerinde release çağrılmadan önce
Guard releasing connectionyazdırılmalıdır.- Constructor'ında bir
main.cpp: Scope'lar aracılığıyla RAII'nin otomatik temizleme özelliğini göster.İki girdi oku:
- Pool capacity (integer)
- İstenen bağlantı sayısı (integer)
Verilen capacity ile bir
ConnectionPoolCreate et. Ardından iç içe bir scope içinde (curly braces kullanarak), istenen sayıdaConnectionGuardnesnesi oluştur ve bunları bir vector'de sakla. Her guard için bağlantının başarılı olup olmadığını yazdır:- Bağlandıysa:
Guard N: Connected - Bağlanmadıysa:
Guard N: Failed to connect
(burada N, 1'den başlar)
Scope sona erdikten sonra (guard'lar destroyed olduğunda), pool'un son durumunu gösteren
After scope: X connections availableifadesini yazdır.
Örneğin, girdiler 2 ve 3 olduğunda:
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 availableGirdiler 3 ve 2 olduğunda:
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 scope dışına çıktığında bağlantıların nasıl otomatik olarak release edildiğine dikkat et—main kodunda hiçbir zaman açıkça release çağırmazsın. Destructor'lar oluşturulma sırasının tersine çalışır (son guard ilk önce destroyed olur) ve acquire edilmiş her bağlantının geri döndürülmesi garanti edilir. RAII'nin gücü budur: kaynak temizliği, scope nasıl sona ererse ersin otomatik ve güvenilir bir şekilde gerçekleşir.
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ştur
// TODO: Süslü parantezler kullanarak iç içe bir kapsam oluştur
{
// TODO: ConnectionGuard nesnelerini saklamak için bir vector oluştur
// İpucu: ConnectionGuard için işaretçiler veya akıllı işaretçiler kullanmanız gerekecek çünkü
// kopya oluşturucusu silinmiş
// TODO: numConnections kadar guard oluşturmak için döngü
// Her guard için şunlardan birini yazdır:
// "Guard N: Connected" or "Guard N: Failed to connect"
// burada N 1'den başlar
}
// Kapsam bittiğinde guard'lar burada yok edilir
// TODO: Print "After scope: X connections available"
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 KonteynerKendi başına pratik yap: Online C++ derleyicisi