Kaynak edinimi başlatılıyor - Resource acquisition is initialization
Kaynak edinimi başlatmadır ( RAII ), belirli bir dil davranışını tanımlamak için çeşitli nesne yönelimli , statik olarak yazılan programlama dillerinde kullanılan bir programlama deyimidir . RAII'de, bir kaynağı tutmak bir sınıf değişmezidir ve nesne ömrüne bağlıdır : kaynak tahsisi (veya edinme), nesne oluşturma (özellikle başlatma) sırasında, yapıcı tarafından yapılırken, kaynağın serbest bırakılması (serbest bırakma) nesne imhası sırasında yapılır ( özellikle sonlandırma), yıkıcı tarafından . Başka bir deyişle, başlatmanın başarılı olması için kaynak edinmenin başarılı olması gerekir. Böylece kaynağın, başlatma bittiğinde ve sonlandırma başladığında (kaynakları tutmak bir sınıf değişmezidir) arasında tutulması ve yalnızca nesne canlı olduğunda tutulması garanti edilir. Böylece nesne sızıntısı yoksa, kaynak sızıntısı da olmaz .
RAII, en belirgin şekilde , kaynaklandığı yerde C++ ile , ayrıca D , Ada , Vala ve Rust ile ilişkilidir . Teknik, 1984-89 yılları arasında C++'da istisna güvenli kaynak yönetimi için, öncelikle Bjarne Stroustrup ve Andrew Koenig tarafından geliştirildi ve terimin kendisi Stroustrup tarafından yapıldı. RAII genellikle bir başlangıç olarak telaffuz edilir , bazen "R, A, çift I" olarak telaffuz edilir.
Bu deyimin diğer adları arasında Yapıcı Alımları , Yıkıcı Sürümleri (CADRe) bulunur ve belirli bir kullanım tarzına Kapsam Tabanlı Kaynak Yönetimi (SBRM) adı verilir. Bu son terim, otomatik değişkenlerin özel durumu içindir . RAII, kaynakları bir kapsamın giriş ve çıkışıyla çakışmayabilecek nesne ömrüne bağlar . (Özellikle ücretsiz mağazada tahsis edilen değişkenlerin belirli bir kapsamla ilgisi olmayan ömürleri vardır.) Ancak, otomatik değişkenler (SBRM) için RAII kullanmak en yaygın kullanım durumudur.
C++11 örneği
Aşağıdaki C++11 örneği, dosya erişimi ve muteks kilitleme için RAII kullanımını gösterir :
#include <fstream>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <string>
void WriteToFile(const std::string& message) {
// |mutex| is to protect access to |file| (which is shared across threads).
static std::mutex mutex;
// Lock |mutex| before accessing |file|.
std::lock_guard<std::mutex> lock(mutex);
// Try to open file.
std::ofstream file("example.txt");
if (!file.is_open()) {
throw std::runtime_error("unable to open file");
}
// Write |message| to |file|.
file << message << std::endl;
// |file| will be closed first when leaving scope (regardless of exception)
// |mutex| will be unlocked second (from lock destructor) when leaving scope
// (regardless of exception).
}
Bu kod istisna açısından güvenlidir, çünkü C++, yığın çözme olarak bilinen, çevreleyen kapsamın sonunda tüm yığın nesnelerinin yok edilmesini garanti eder . Her iki yıkıcılar kilit ve dosya nesneleri dolayısıyla işlevinden döndürürken bir istisna atılır ya da olmasın edilip, çağrılacak garantilidir.
Yerel değişkenler, tek bir işlev içinde birden çok kaynağın kolay yönetimine izin verir: Yapılışlarının tersi sırada yok edilirler ve bir nesne yalnızca tam olarak oluşturulmuşsa, yani yapıcısından hiçbir istisna yayılmazsa yok edilir.
RAII kullanmak, kaynak yönetimini büyük ölçüde basitleştirir, genel kod boyutunu azaltır ve programın doğruluğunu sağlamaya yardımcı olur. Bu nedenle RAII, endüstri standardı yönergeler tarafından önerilir ve C++ standart kitaplığının çoğu deyimi takip eder.
Faydalar
Bir kaynak yönetimi tekniği olarak RAII'nin avantajları, kapsülleme, istisna güvenliği (yığın kaynakları için) ve yerellik (alma ve bırakma mantığının yan yana yazılmasına izin vermesi) sağlamasıdır.
Kapsülleme sağlanır, çünkü kaynak yönetimi mantığı her çağrı sitesinde değil, sınıfta bir kez tanımlanır. Kaynak bir yığın değişkeninin (belirli bir kapsamda bildirilen yerel bir değişken) kullanım ömrüne bağlanarak yığın kaynakları (alındıkları kapsamda yayımlanan kaynaklar) için özel durum güvenliği sağlanır: bir istisna atılırsa ve uygun istisna işleme mevcutsa, geçerli kapsamdan çıkarken yürütülecek olan tek kod, o kapsamda bildirilen nesnelerin yıkıcılarıdır. Son olarak sınıf tanımında yapıcı ve yıkıcı tanımları yan yana yazılarak tanımın yerelliği sağlanır.
Bu nedenle kaynak yönetiminin, otomatik tahsis ve iyileştirme elde etmek için uygun nesnelerin ömrüne bağlanması gerekir. Kaynaklar, kullanıma sunulmadan önce kullanılma şansları olmadığında başlatma sırasında elde edilir ve hata durumunda bile gerçekleşeceği garanti edilen aynı nesnelerin imhasıyla serbest bırakılır.
RAII'yi finallyJava'da kullanılan yapı ile karşılaştıran Stroustrup, “Gerçekçi sistemlerde, kaynak türlerinden çok daha fazla kaynak alımı vardır, bu nedenle 'kaynak edinimi başlatmadır' tekniği, 'nihayet' yapısının kullanılmasından daha az koda yol açar. ”
Tipik kullanımlar
RAII tasarımı genellikle çok iş parçacıklı uygulamalarda muteks kilitlerini kontrol etmek için kullanılır . Bu kullanımda, nesne yok edildiğinde kilidi serbest bırakır. Bu senaryoda RAII olmadan, kilitlenme potansiyeli yüksek olacaktır ve mutex'i kilitleme mantığı, onu açma mantığından uzak olacaktır. RAII ile, mutex'i kilitleyen kod, esasen, yürütme RAII nesnesinin kapsamından çıktığında kilidin serbest bırakılacağı mantığını içerir.
Diğer bir tipik örnek, dosyalarla etkileşimdir: Yazmaya açık bir dosyayı temsil eden bir nesneye sahip olabiliriz, burada dosya yapıcıda açılır ve yürütme nesnenin kapsamından çıktığında kapatılır. Her iki durumda da RAII, yalnızca söz konusu kaynağın uygun şekilde serbest bırakılmasını sağlar; istisna güvenliğini sağlamak için yine de özen gösterilmelidir. Veri yapısını veya dosyayı değiştiren kod istisna açısından güvenli değilse, muteksin kilidi açılabilir veya veri yapısı veya dosya bozularak dosya kapatılabilir.
Dinamik olarak tahsis edilen nesnelerin ( newC++ ile tahsis edilen bellek) mülkiyeti , RAII (yığın tabanlı) nesnesi yok edildiğinde nesnenin serbest bırakılacağı şekilde RAII ile de kontrol edilebilir. Bu amaçla, C++11 standart kitaplığı, tek sahip olunan nesneler ve paylaşılan mülkiyete sahip nesneler için akıllı işaretçi sınıflarını tanımlar . Benzer dersleri de edinilebilir C ++ 98 yılında ve içinde Boost kütüphaneleri .
std::unique_ptrstd::shared_ptrstd::auto_ptrboost::shared_ptr
Derleyici "temizleme" uzantıları
Hem Clang hem de GNU Derleyici Koleksiyonu , RAII'yi desteklemek için C diline standart olmayan bir uzantı uygular : "temizleme" değişken özelliği. Aşağıdaki makro , değişken kapsam dışına çıktığında çağıracağı belirli bir yıkıcı işlevi olan bir değişkene açıklama ekler:
static inline void fclosep(FILE **fp) { if (*fp) fclose(*fp); }
#define _cleanup_fclose_ __attribute__((cleanup(fclosep)))
Bu makro daha sonra aşağıdaki gibi kullanılabilir:
void example_usage() {
_cleanup_fclose_ FILE *logfile = fopen("logfile.txt", "w+");
fputs("hello logfile!", logfile);
}
Bu örnekte, derleyici, example_usage döndürülmeden önce günlük dosyasında çağrılacak fclosep işlevini düzenler .
sınırlamalar
RAII sadece alınan ve orada yığın ayrılan nesne tarafından (doğrudan ya da dolaylı olarak) serbest kaynak işleri olan iyi tanımlanmış bir statik bir amacı süresi. Kaynakları alan ve serbest bırakan yığınla ayrılmış nesneler, C++ dahil olmak üzere birçok dilde yaygındır. RAII, kaynak salan yıkıcısını (veya eşdeğerini) tetiklemek için tüm olası yürütme yolları boyunca örtülü veya açık bir şekilde silinecek yığın tabanlı nesnelere bağlıdır. Bu, döngüsel olarak başvurulan nesneler için zayıf işaretçiler ile tüm yığın nesnelerini yönetmek için akıllı işaretçiler kullanılarak başarılabilir.
C++'da yığın çözmenin yalnızca istisna bir yerde yakalanırsa gerçekleşmesi garanti edilir. Bunun nedeni, "Bir programda eşleşen bir işleyici bulunmazsa, sonlandırma() işlevi çağrılır; bu sonlandırma() çağrısından önce yığının çözülüp çözülmediği uygulama tanımlıdır (15.5.1). (C++03 standardı, §15.3/9). Bu davranış genellikle kabul edilebilir, çünkü işletim sistemi program sonlandırıldığında bellek, dosyalar, yuvalar vb. gibi kalan kaynakları serbest bırakır.
Referans sayımı
Perl , Python ( CPython uygulamasında) ve PHP , RAII kullanımını mümkün kılan referans sayımı ile nesne ömrünü yönetir . Artık başvurulmayan nesneler hemen yok edilir veya sonlandırılır ve serbest bırakılır, böylece bir yıkıcı veya sonlandırıcı o anda kaynağı serbest bırakabilir. Ancak, bu tür dilde her zaman deyimsel değildir ve özellikle (lehinde Python şekilde önerilmez bağlam yöneticileri ve finalizers gelen weakref pakette).
Bununla birlikte, nesne yaşam süreleri mutlaka herhangi bir kapsama bağlı değildir ve nesneler deterministik olmayan bir şekilde veya hiç yok edilmeyebilir. Bu, bazı kapsamın sonunda serbest bırakılması gereken kaynakların yanlışlıkla sızdırılmasını mümkün kılar. Statik bir değişkende (özellikle bir global değişkende ) depolanan nesneler , program sonlandırıldığında sonlandırılamayabilir, bu nedenle kaynakları serbest bırakılmaz; CPython, örneğin, bu tür nesneleri sonlandırmayı garanti etmez. Ayrıca, dairesel referansları olan nesneler basit bir referans sayacı tarafından toplanmayacak ve belirsiz bir şekilde uzun yaşayacaktır; toplanmış olsa bile (daha karmaşık çöp toplama ile), imha süresi ve imha sırası deterministik olmayacaktır. CPython'da, döngüleri algılayan ve döngüdeki nesneleri sonlandıran bir döngü dedektörü vardır, ancak CPython 3.4'ten önce döngüdeki herhangi bir nesnenin bir sonlandırıcısı varsa döngüler toplanmaz.
Referanslar
daha fazla okuma
- Stroustrup, Bjarne (1994). C++'ın Tasarımı ve Evrimi . Addison-Wesley. Bibcode : 1994dec..book.....S . ISBN'si 978-0-201-54330-8.
Dış bağlantılar
- Örnek Bölüm: Stephen C. Dewhurst tarafından " Anlatılan #67: Kaynak Edinimi Çalıştırma Başarısızlığı Başlatmadır "
- Röportaj: Bill Venners tarafından " Bjarne Stroustrup ile Bir Konuşma "
- HABER: Bjorn Karlsson ve Matthew Wilson tarafından " The Law of The Big Two "
- HABER: " 'Kaynak Alımı Başlatmadır' Deyimini Uygulamak " Danny Kalev tarafından
- Makale: " RAII, Dinamik Nesneler ve C++ 'da Fabrikalar " Roland Pibinger tarafından
- Delphi'de RAII : Barry Kelly tarafından " Delphi'de tek satırlı RAII "