Terimin ve kavramın anlamını anlama - RAII (Kaynak Edinme Başlatma)


110

C ++ geliştiricileri, lütfen bize RAII'nin ne olduğu, neden önemli olduğu ve diğer dillerle herhangi bir ilgisi olup olmadığı hakkında iyi bir açıklama verebilir misiniz?

Ben bunu biraz biliyorum. Bunun "Kaynak Edinme Başlatma" anlamına geldiğine inanıyorum. Bununla birlikte, bu ad, RAII'nin ne olduğunu (muhtemelen yanlış) anladığımla uyuşmuyor: RAII'nin yığın üzerindeki nesneleri başlatmanın bir yolu olduğu izlenimini alıyorum, öyle ki, bu değişkenler kapsam dışına çıktığında, yıkıcılar otomatik olarak kaynakların temizlenmesine neden olacak şekilde çağrılabilir.

Öyleyse neden buna "temizlemeyi tetiklemek için yığını kullanma" (UTSTTC :) denmiyor? Oradan "RAII" e nasıl gidilir?

Ve yığın üzerinde yaşayan bir şeyin temizlenmesine neden olacak bir şeyi nasıl yapabilirsiniz? Ayrıca RAII'yi kullanamayacağınız durumlar var mı? Kendinizi hiç çöp toplama dilerken buldunuz mu? En azından bazı nesneler için diğerlerinin yönetilmesine izin verirken kullanabileceğiniz bir çöp toplayıcı?

Teşekkürler.


27
UTSTTC? Bunu sevdim! RAII'den çok daha sezgisel. RAII'nin adı kötü, herhangi bir C ++ programcısının buna itiraz edeceğinden şüpheliyim. Ama değiştirmek kolay değil. ;)
jalf

10
Stroustrup'ın konuyla ilgili görüşünü burada bulabilirsiniz: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi

3
@sbi: Her neyse, sadece tarihsel araştırma için yorumunuza +1. Yazarın (B. Stroustrup) bir kavramın adı (RAII) üzerine bakış açısına sahip olmanın, kendi cevabını alacak kadar ilginç olduğuna inanıyorum.
paercebal

1
@paercebal: Tarihsel araştırma? Şimdi beni çok yaşlı hissettirdin. :(O zamanlar tüm konuyu okuyordum ve kendimi bir C ++ acemi olarak bile görmedim!
sbi

3
+1, ben de aynı soruyu sormak üzereydim, neyse ki kavramı anlayan ama adını anlamayan tek kişi ben değilim. Görünüşe göre RAOI - Başlatma Üzerine Kaynak Edinme olarak adlandırılmış olmalı.
laurent

Yanıtlar:


132

Öyleyse neden buna "temizlemeyi tetiklemek için yığını kullanma" (UTSTTC :) denmiyor?

RAII size ne yapmanız gerektiğini söylüyor: Kaynağınızı bir kurucuda edinin! Ekleyeceğim: bir kaynak, bir kurucu. UTSTTC bunun sadece bir uygulamasıdır, RAII çok daha fazlasıdır.

Kaynak Yönetimi berbat. Burada kaynak, kullanımdan sonra temizlenmesi gereken her şeydir. Pek çok platformdaki projeler üzerinde yapılan araştırmalar, hataların çoğunun kaynak yönetimi ile ilgili olduğunu ve özellikle Windows'ta kötü olduğunu (birçok nesne ve ayırıcı türü nedeniyle) gösteriyor.

C ++ 'da, istisnalar ve (C ++ stili) şablonların birleşimi nedeniyle kaynak yönetimi özellikle karmaşıktır. Kaputun altına bir göz atmak için bkz. GOTW8 ).


C ++, yıkıcının ancak ve ancak kurucu başarılı olursa çağrılacağını garanti eder . Buna dayanarak RAII, ortalama bir programcının farkında bile olmayabileceği birçok çirkin sorunu çözebilir. İşte "yerel değişkenlerim her geri döndüğümde yok edilecek" dışında birkaç örnek.

FileHandleRAII kullanan aşırı basit bir sınıfla başlayalım :

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

İnşaat başarısız olursa (istisna dışında), başka hiçbir üye işlevi - yıkıcı bile - çağrılmaz.

RAII, geçersiz durumdaki nesneleri kullanmaktan kaçınır. biz nesneyi kullanmadan önce hayatı zaten kolaylaştırıyor.

Şimdi geçici nesnelere bir göz atalım:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

Ele alınacak üç hata durumu vardır: hiçbir dosya açılamaz, yalnızca bir dosya açılabilir, her iki dosya da açılabilir ancak dosyalar kopyalanamadı. RAII olmayan bir uygulamada, Fooher üç durumu da açıkça ele almak gerekir.

RAII, tek bir ifadede birden çok kaynak elde edilse bile, edinilen kaynakları serbest bırakır.

Şimdi, bazı nesneleri bir araya getirelim:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

Yapıcısı Loggereğer başarısız olur original'(çünkü s yapıcı başarısız filename1açılamadı), duplex(çünkü s yapıcı başarısız' filename2açılamadı) veya içindeki dosyalara yazma Logger'ın yapıcı vücut başarısız olur. Bu durumların herhangi birinde, Logger's yıkıcı olacak değil çağrılabilir - biz güvenemez, böylece Logger' dosyalarını yayınlayacak s yıkıcı. Ancak originalinşa edilmişse , yıkıcısı yapıcının temizliği sırasında çağrılacaktır Logger.

RAII, kısmi inşaattan sonra temizlemeyi basitleştirir.


Olumsuz noktalar:

Negatif noktalar? Tüm sorunlar RAII ve akıllı işaretçilerle çözülebilir ;-)

RAII, toplanan nesneleri yığının üzerine iterek, edinimi geciktirmeniz gerektiğinde bazen hantaldır.
Logger'ın a SetTargetFile(const char* target). Bu durumda, hala üyesi olması gereken tutamacın Loggeryığın üzerinde bulunması gerekir (örneğin, tutamacın imhasını uygun şekilde tetiklemek için akıllı bir işaretçide).

Asla çöplerin toplanmasını istemedim. C # yaptığımda bazen, umursamama gerek duymadığım bir mutluluk anı hissediyorum, ama daha çok deterministik yıkım yoluyla yaratılabilecek tüm havalı oyuncakları özlüyorum. ( IDisposablesadece kullanmak onu kesmez.)

GC'den yararlanabilecek özellikle karmaşık bir yapıya sahiptim, burada "basit" akıllı işaretçiler birden çok sınıf üzerinde döngüsel referanslara neden olur. Güçlü ve zayıf işaretçileri dikkatlice dengeleyerek ilerledik, ancak ne zaman bir şeyi değiştirmek istersek, büyük bir ilişki tablosu incelemeliyiz. GC daha iyi olabilirdi, ancak bileşenlerin bazıları en kısa sürede yayınlanması gereken kaynakları barındırıyordu.


FileHandle örneğiyle ilgili bir not: Tam olması amaçlanmadı, yalnızca bir örnek - ancak yanlış çıktı. Bunu doğru bir C ++ 0x çözümüne dönüştürdüğü için Johannes Schaub'a ve FredOverflow'a teşekkür ederiz. Zamanla, burada belgelenen yaklaşıma karar verdim .


1
+1 GC ve ASAP'ın iç içe geçmediğini işaret etmek için. Sık sık acıtmaz ama
acımadığında

10
Özellikle önceki okumalarda gözden kaçırdığım bir cümle. "RAII" nin size "Kaynaklarınızı inşaatçılar içinden elde edin" dediğini söylediniz. Bu mantıklı ve neredeyse "RAII" nin kelimesi kelimesine bir yorumudur. Şimdi daha da iyi anlıyorum (Mümkünse size tekrar oy verirdim :)
Charlie Flowers

2
GC'nin önemli bir avantajı, bellek ayırma çerçevesinin "güvenli olmayan" kod yokluğunda sarkan referansların oluşturulmasını engelleyebilmesidir (eğer "güvenli olmayan" koda izin verilirse, tabii ki çerçeve hiçbir şeyi önleyemez). GC, genellikle açık bir sahibi olmayan ve temizleme gerektirmeyen dizeler gibi paylaşılan değişmez nesnelerle uğraşırken RAII'den daha üstündür . Daha fazla çerçevenin GC ve RAII'yi birleştirmeye çalışmaması talihsiz bir durumdur, çünkü çoğu uygulama değişmez nesnelerin bir karışımına (GC'nin en iyisi olduğu yer) ve temizlenmesi gereken nesnelerin (RAII'nin en iyisi olduğu yerde) sahip olacaktır.
supercat

@supercat: Genelde GC'yi severim - ancak yalnızca GC'nin "anladığı" kaynaklar için çalışır. Örneğin, .NET GC, COM nesnelerinin maliyetini bilmez. Bunları bir döngüde basitçe oluşturup yok ederken, mutlu bir şekilde uygulamanın adres alanı veya sanal bellek ile ilgili zemine oturmasına izin verir - önce ne olursa olsun - bir GC yapmayı bile düşünmeden. --- dahası, mükemmel bir GC'lenmiş ortamda bile, deterministik yıkımın gücünü hala özlüyorum: aynı kalıbı diğer yapaylıklara uygulayabilirsiniz, örneğin, belirli koşullar altında UI öğelerini gösterebilirsiniz.
peterchen

@peterchen: Pek çok OOP ile ilgili düşüncede eksik olduğunu düşündüğüm bir şey, nesne sahipliği kavramı. Sahipliğin kaydını tutmak, genellikle kaynakları olan nesneler için açıkça gereklidir, ancak aynı zamanda kaynakları olmayan değiştirilebilir nesneler için de gereklidir. Genel olarak, nesneler değiştirilebilir durumlarını ya muhtemelen paylaşılan değişmez nesnelere referanslarda ya da münhasır sahibi oldukları değiştirilebilir nesnelerde kapsüllemelidir. Böyle seçkin mülkiyet mutlaka özel yazma erişimi anlamına gelmez, ancak eğer Foosahibi Barve Bozmutasyon o ...
SuperCat

42

Orada mükemmel cevaplar var, bu yüzden unuttuğum bazı şeyleri ekliyorum.

0. RAII kapsamlarla ilgilidir

RAII ikisiyle de ilgilidir:

  1. yapıcıda bir kaynak elde etmek (hangi kaynak olursa olsun) ve yıkıcıda onu geri almak.
  2. değişken bildirildiğinde yapıcının çalıştırılması ve yıkıcının değişken kapsam dışına çıktığında otomatik olarak yürütülmesi.

Diğerleri bunu zaten cevapladı, bu yüzden ayrıntıya girmeyeceğim.

1. Java veya C # ile kodlarken, zaten RAII kullanıyorsunuz ...

MONSIEUR JOURDAIN: Ne! "Nicole, terliklerimi getir ve içkimi bana ver" dediğimde, bu düz yazı mı?

PHILOSOPHY MASTER: Evet efendim.

MONSIEUR JOURDAIN: Kırk yıldan fazla bir süredir onun hakkında hiçbir şey bilmeden düz yazı konuşuyorum ve bunu bana öğrettiğim için sana çok minnettarım.

- Molière: Orta Sınıf Beyefendi, Perde 2, Sahne 4

Mösyö Jourdain'in düzyazıda yaptığı gibi, C # ve hatta Java insanları zaten RAII kullanıyor, ancak gizli yollarla. Örneğin, aşağıdaki Java kodu (aynı şekilde C # ile değiştirilerek yazılır)synchronized ile lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... zaten RAII kullanıyor: Mutex edinimi (synchronized veya lock) ve edinme kapsamdan çıkılırken yapılacaktır.

RAII'yi hiç duymamış insanlar için bile neredeyse hiçbir açıklama gerektirmeyecek kadar doğal.

C ++ 'nın Java ve C # üzerinde sahip olduğu avantaj, RAII kullanılarak her şeyin yapılabilmesidir. Örneğin, doğrudan yerleşik eşdeğeri yoktursynchronized ne de lockC ++, ama biz hala onları olabilir.

C ++ 'da şöyle yazılır:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

Java / C # yoluyla kolayca yazılabilir (C ++ makroları kullanılarak):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII'nin alternatif kullanımları vardır

BEYAZ TAVŞAN: [şarkı söyler] Geç kaldım / Geç kaldım / Çok önemli bir tarih için. / "Merhaba" demeye zaman yok. / Güle güle. / Geç kaldım, geç kaldım, geç kaldım.

- Alice Harikalar Diyarında (Disney versiyonu, 1951)

Yapıcının ne zaman çağrılacağını (nesne bildiriminde) bilirsiniz ve karşılık gelen yıkıcının (kapsamın çıkışında) ne zaman çağrılacağını bilirsiniz, böylece sadece bir satırla neredeyse sihirli bir kod yazabilirsiniz. C ++ harikalar diyarına hoş geldiniz (en azından bir C ++ geliştiricisinin bakış açısından).

Örneğin, bir sayaç nesnesi yazabilir (bunu bir alıştırma olarak kabul ettim) ve yukarıdaki kilit nesnesinin kullanıldığı gibi sadece değişkenini bildirerek kullanabilirsiniz:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

Tabii ki, yine bir makro kullanılarak Java / C # yolu ile yazılabilir:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. C ++ neden eksik finally?

[BAĞIRMA] Bu son geri sayım!

- Avrupa: Son Geri Sayım (üzgünüm, burada alıntılar kalmamıştı ... :-)

finallyFıkra kapsamı çıkışında durumunda kaynak bertarafını işlemek için C # / Java kullanılır (bir içinden returnveya atılmış istisna).

Astute belirtim okuyucuları, C ++ 'nın nihayet cümlesi olmadığını fark edeceklerdir. Ve bu bir hata değildir, çünkü RAII zaten kaynak kullanımının üstesinden geldiğinden C ++ buna ihtiyaç duymaz. (Ve inan bana, bir C ++ yıkıcı yazmak, doğru Java nihayet cümlesini veya hatta bir C # 'ın doğru Dispose yöntemini yazmaktan çok daha kolaydır).

Yine de bazen bir finallycümle harika olurdu. Bunu C ++ ile yapabilir miyiz? Evet yapabiliriz! Ve yine alternatif bir RAII kullanımıyla.

Sonuç: RAII, C ++ 'da bir felsefeden daha fazlasıdır: C ++' dır

RAII? BU C ++ IS !!!

- C ++ geliştiricisinin öfkeli yorumu, belirsiz bir Sparta kralı ve 300 arkadaşı tarafından utanmadan kopyalandı

C ++ 'da belirli bir deneyim düzeyine ulaştığınızda , dönüştürücü ve yıkıcıların otomatikleştirilmiş yürütmesi açısından RAII açısından düşünmeye başlarsınız .

Kapsamlar açısından düşünmeye başlarsınız ve {ve }karakterleri kodunuzda en önemlilerinden biri haline gelir.

Ve hemen hemen her şey RAII açısından tam olarak uyuyor: istisna güvenliği, muteksler, veritabanı bağlantıları, veritabanı istekleri, sunucu bağlantısı, saatler, işletim sistemi tutamaçları vb. Ve son olarak, ama en önemlisi, bellek.

Veritabanı kısmı göz ardı edilemez, çünkü fiyatı ödemeyi kabul ederseniz, bir " işlemsel programlama " tarzında yazabilirsiniz , sonunda tüm değişiklikleri yapmak isteyip istemediğinize karar verene kadar satırları ve satırları çalıştırabilirsiniz. veya mümkün değilse, tüm değişikliklerin geri alınması (her satır en azından Güçlü İstisna Garantisini karşıladığı sürece). (bu Herb's Sutter makalesinin ikinci bölümüne bakın işlemsel programlama için ).

Ve bir bulmaca gibi her şey uyuyor.

RAII, C ++ 'nın o kadar büyük bir parçası ki, C ++ onsuz C ++ olamaz.

Bu, deneyimli C ++ geliştiricilerinin neden RAII'ye bu kadar aşık olduğunu ve başka bir dili denerken neden ilk aradıkları şeyin RAII olduğunu açıklıyor.

Ve Çöp Toplayıcının kendi başına muhteşem bir teknoloji parçası olmasına rağmen, bir C ++ geliştiricisinin bakış açısından neden bu kadar etkileyici olmadığını açıklıyor:

  • RAII zaten bir GC tarafından ele alınan vakaların çoğunu ele alıyor
  • Bir GC, saf yönetilen nesneler üzerinde döngüsel referanslarla RAII'den daha iyi işlem yapar (zayıf işaretçilerin akıllı kullanımıyla azaltılır)
  • Yine de GC bellekle sınırlıdır, RAII ise her tür kaynağı idare edebilir.
  • Yukarıda açıklandığı gibi, RAII çok daha fazlasını yapabilir ...

Bir Java hayranı: GC'nin RAII'den çok daha kullanışlı olduğunu söyleyebilirim çünkü tüm belleği işliyor ve sizi birçok olası hatadan kurtarıyor. GC ile döngüsel referanslar oluşturabilir, referansları iade edebilir ve saklayabilirsiniz ve yanlış anlaşılması zordur (sözde kısa ömürlü bir nesneye referans depolamak, bir tür bellek sızıntısı olan canlı süresini uzatır, ancak tek sorun budur) . Kaynakları GC ile kullanmak işe yaramaz, ancak bir uygulamadaki çoğu kaynağın önemsiz bir canlı döngüsü vardır ve kalan birkaç tanesi çok önemli değildir. Keşke hem GC hem de RAII'ye sahip olsaydık, ama bu imkansız gibi görünüyor.
maaartinus

16

1
Bunlardan bazıları sorumla tam uyumlu, ancak ne bir arama onları ne de yeni bir soru girdikten sonra görünen "ilgili sorular" listesi. Bağlantılar için teşekkürler.
Charlie Flowers

1
@Charlie: Aramadaki yapı bazı açılardan çok zayıf. Etiket sözdizimini ("[konu]") kullanmak çok yararlıdır ve birçok kişi google ...
dmckee --- eski moderatör kedicik

10

RAII, kaynakları yönetmek için C ++ yıkıcı semantiğini kullanıyor. Örneğin, akıllı bir işaretçiyi düşünün. Bu işaretçiyi nesnenin adresiyle başlatan parametreleştirilmiş bir göstericiye sahipsiniz. Yığın üzerine bir işaretçi tahsis edersiniz:

SmartPointer pointer( new ObjectClass() );

Akıllı işaretçi kapsam dışına çıktığında, işaretçi sınıfının yıkıcısı bağlı nesneyi siler. İşaretçi yığın olarak ayrılır ve nesne yığın olarak ayrılır.

RAII'nin yardımcı olmadığı bazı durumlar vardır. Örneğin, referans sayan akıllı işaretçiler kullanırsanız (boost :: shared_ptr gibi) ve bir döngü ile grafik benzeri bir yapı oluşturursanız, bir döngüdeki nesneler birbirlerinin serbest bırakılmasını önleyeceği için bir bellek sızıntısı ile karşılaşma riskiyle karşı karşıya kalırsınız. Çöp toplama buna karşı yardımcı olacaktır.


2
Bu yüzden UCDSTMR olarak adlandırılmalı :)
Daniel Daranas

İkinci bir düşünceye göre, UDSTMR'nin daha uygun olduğunu düşünüyorum. Dil (C ++) verilmiştir, bu nedenle kısaltmada "C" harfine gerek yoktur. UDSTMR, Kaynakları Yönetmek İçin Destructor Semantiğini Kullanma anlamına gelir.
Daniel Daranas

9

Bunu önceki yanıtlardan biraz daha güçlü bir şekilde ifade etmek istiyorum.

RAII, Resource Acquisition Is Initialization , elde edilen tüm kaynakların bir nesnenin başlatılması bağlamında edinilmesi gerektiği anlamına gelir. Bu, "çıplak" kaynak edinimini yasaklar. Mantık, C ++ 'da temizlemenin işlev çağrısı temelinde değil, nesne bazında çalışmasıdır. Bu nedenle, tüm temizlik işlev çağrıları tarafından değil nesneler tarafından yapılmalıdır. Bu anlamda C ++, Java'dan daha çok nesneye yöneliktir. Java temizleme, finallymaddelerdeki işlev çağrılarına dayanır .


Mükemmel cevap. Ve "bir nesnenin ilklendirilmesi", "oluşturucular" anlamına gelir, değil mi?
Charlie Flowers

@Charlie: evet, özellikle bu durumda.
MSalters

8

Cpitis ile aynı fikirdeyim. Ancak, kaynakların sadece bellek olamayacağını da eklemek isterim. Kaynak, bir dosya, kritik bir bölüm, bir iş parçacığı veya bir veritabanı bağlantısı olabilir.

Kaynak Edinme Başlatma olarak adlandırılır çünkü kaynak, kaynağı kontrol eden nesne inşa edildiğinde elde edilir, Oluşturucu başarısız olursa (yani bir istisna nedeniyle) kaynak elde edilmez. Ardından nesne kapsam dışına çıktığında kaynak serbest bırakılır. c ++, yığındaki başarılı bir şekilde oluşturulmuş tüm nesnelerin yok edileceğini garanti eder (bu, süper sınıf yapıcısı başarısız olsa bile temel sınıfların ve üyelerin yapıcılarını içerir).

RAII'nin arkasındaki mantık, kaynak edinme istisnasını güvenli hale getirmektir. Bir istisna nerede olursa olsun, elde edilen tüm kaynakların uygun şekilde serbest bırakılması. Ancak bu, kaynağı alan sınıfın kalitesine bağlıdır (bu istisnai güvenli olmalıdır ve bu zordur).


Mükemmel, ismin arkasındaki mantığı açıkladığınız için teşekkür ederim. Anladığım kadarıyla, RAII'yi "(kurucu tabanlı) başlatma dışında herhangi bir mekanizma yoluyla hiçbir zaman kaynak edinmeyin" şeklinde yorumlayabilirsiniz. Evet?
Charlie Flowers

Evet, bu benim politikam, ancak istisnai güvenli olmaları gerektiğinden kendi RAII sınıflarımı yazmaktan çok çekiniyorum. Bunları yazdığımda, uzmanlar tarafından yazılan diğer RAII sınıflarını yeniden kullanarak istisna güvenliği sağlamaya çalışıyorum.
iain

Onları yazmakta zor bulmadım. Sınıflarınız yeterince küçükse, hiç de zor değildir.
Rob K

7

Çöp toplamayla ilgili sorun, RAII için çok önemli olan deterministik yıkımı kaybetmenizdir. Bir değişken kapsam dışına çıktığında, nesnenin ne zaman geri alınacağı çöp toplayıcısına bağlıdır. Nesne tarafından tutulan kaynak, yıkıcı çağrılıncaya kadar tutulmaya devam edecektir.


4
Sorun sadece determinizm değil. Gerçek sorun, sonlandırıcıların (java adlandırma) GC'nin önüne geçmesidir. GC verimlidir çünkü ölü nesneleri hatırlamaz, aksine onları unutup yok eder. GC'ler, adlandırılmalarını garanti etmek için sonlandırıcılarla nesneleri farklı bir şekilde izlemelidir
David Rodríguez - dribeas

1
java / c # dışında, muhtemelen bir sonlandırıcı yerine nihayet bloğu temizlersiniz.
jk.

4

RAII, Kaynak Tahsisi Başlatma'dan gelir. Temel olarak, bir yapıcı yürütmeyi bitirdiğinde, oluşturulan nesnenin tamamen başlatıldığı ve kullanıma hazır olduğu anlamına gelir. Ayrıca, yıkıcının nesnenin sahip olduğu herhangi bir kaynağı (ör. Bellek, işletim sistemi kaynakları) serbest bırakacağı anlamına gelir.

Çöp toplanan diller / teknolojiler (örn. Java, .NET) ile karşılaştırıldığında, C ++ bir nesnenin ömrü üzerinde tam kontrol sağlar. Yığın tahsis edilmiş bir nesne için, nesnenin yıkıcısının ne zaman çağrılacağını (yürütme kapsam dışına çıktığında), çöp toplama durumunda gerçekten kontrol edilmeyen şeyi bilirsiniz. C ++ 'da akıllı işaretçiler kullansanız bile (örn. Boost :: shared_ptr), sivri uçlu nesneye referans olmadığında, o nesnenin yıkıcısının çağrılacağını bilirsiniz.


3

Ve yığın üzerinde yaşayan bir şeyin temizlenmesine neden olacak bir şeyi nasıl yapabilirsiniz?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

Bir int_buffer örneği ortaya çıktığında, bir boyuta sahip olması gerekir ve gerekli belleği tahsis eder. Kapsam dışına çıktığında yıkıcı denir. Bu, senkronizasyon nesneleri gibi şeyler için çok kullanışlıdır. Düşünmek

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Ayrıca RAII'yi kullanamayacağınız durumlar var mı?

Hayır gerçek değil.

Kendinizi hiç çöp toplama dilerken buldunuz mu? En azından bazı nesneler için diğerlerinin yönetilmesine izin verirken kullanabileceğiniz bir çöp toplayıcı?

Asla. Çöp toplama, yalnızca çok küçük bir dinamik kaynak yönetimi alt kümesini çözer.


Java ve C #'yi çok az kullandım, bu yüzden asla kaçırmadım, ancak GC onları kullanmak zorunda olduğumda kaynak yönetimi söz konusu olduğunda kesinlikle tarzımı sıkıştı, çünkü RAII kullanamıyordum.
Rob K

1
C # 'ı çok kullandım ve sana% 100 katılıyorum. Aslında, deterministik olmayan bir GC'nin bir dilde bir yükümlülük olduğunu düşünüyorum.
Nemanja Trifunovic

2

Burada zaten birçok iyi cevap var, ancak eklemek istiyorum:
RAII'nin basit bir açıklaması, C ++ 'da, yığın üzerinde tahsis edilen bir nesnenin kapsam dışına çıktığında yok edilmesidir. Bu, bir nesne yok edicisinin çağrılacağı ve gerekli tüm temizliği yapabileceği anlamına gelir.
Yani, bir nesne "yeni" olmadan oluşturulursa "silme" gerekmez. Ve bu aynı zamanda "akıllı işaretçilerin" arkasındaki fikirdir - yığın üzerinde bulunurlar ve temelde yığın tabanlı bir nesneyi sararlar.


1
Hayır, yok. Ancak yığın üzerinde akıllı bir işaretçi oluşturmak için iyi bir nedeniniz var mı? Bu arada, akıllı işaretçi, RAII'nin yararlı olabileceği yerlerin sadece bir örneğiydi.
E Dominique

1
Belki "yığın" ve "yığın" kullanımım biraz özensizdir - "yığın" üzerindeki bir nesneden herhangi bir yerel nesneyi kastetmiştim. Doğal olarak bir nesnenin bir parçası olabilir, örneğin yığın üzerinde. "Yığın üzerinde akıllı bir işaretçi oluştur" ile, akıllı işaretçinin kendisinde yeni / sil kullanmayı kastetmiştim.
E Dominique

1

RAII, Resource Acquisition Is Initialization'ın kısaltmasıdır.

Bu teknik, hem Oluşturucular hem de Yıkıcılar için ve neredeyse otomatik olarak iletilen argümanlarla eşleşen oluşturuculara yönelik destekleri nedeniyle C ++ 'ya çok özeldir veya en kötü durumda, eğer sağlanan açıklık aksi takdirde varsayılan olarak adlandırılırsa varsayılan kurucu ve yıkıcılar olarak adlandırılır. C ++ derleyicisi tarafından eklenen, bir C ++ sınıfı için açıkça bir yıkıcı yazmadıysanız çağrılır. Bu yalnızca otomatik olarak yönetilen C ++ nesneleri için olur - yani boş depoyu kullanmayanlar (yeni, yeni [] / delete, delete [] C ++ operatörleri kullanılarak ayrılan / ayrılmış bellek).

RAII tekniği, yeni / yeni [] kullanarak açıkça daha fazla bellek isteyerek yığın / serbest depoda oluşturulan nesneleri işlemek için bu otomatik yönetilen nesne özelliğini kullanır; sil / sil [] çağrısı ile açıkça yok edilmesi gerekir. . Otomatik yönetilen nesnenin sınıfı, bu diğer nesneyi yığın / boş bellek belleğinde oluşturulan başka bir nesneyi sarmalar. Bu nedenle, otomatik yönetilen nesnenin yapıcısı çalıştırıldığında, sarılmış nesne yığın / serbest depolama belleğinde oluşturulur ve otomatik yönetilen nesnenin tutamacı kapsam dışına çıktığında, otomatik olarak yönetilen nesnenin yıkıcısı otomatik olarak çağrılır nesne silme kullanılarak yok edilir. OOP kavramlarıyla, bu tür nesneleri özel kapsamdaki başka bir sınıfın içine sararsanız, sarmalanmış sınıf üyelerine ve yöntemlerine erişemezsiniz. Akıllı işaretçilerin (diğer adıyla tutamaç sınıfları) tasarlanmasının nedeni budur. Bu akıllı işaretçiler, sarılmış nesneyi, dış dünyaya ve orada, maruz kalan bellek nesnesinin oluşturduğu herhangi bir üyeyi / yöntemi çağırmaya izin vererek, yazılan nesne olarak ortaya çıkarır. Akıllı işaretçilerin farklı ihtiyaçlara göre çeşitli tatları olduğunu unutmayın. Hakkında daha fazla bilgi edinmek için Andrei Alexandrescu tarafından hazırlanan Modern C ++ programlamasına başvurmalı veya kitaplığın (www.boostorg) shared_ptr.hpp uygulamasını / belgelerini artırmalısınız. Umarım bu, RAII'yi anlamanıza yardımcı olur. Hakkında daha fazla bilgi edinmek için Andrei Alexandrescu tarafından hazırlanan Modern C ++ programlamasına başvurmalı veya kitaplığın (www.boostorg) shared_ptr.hpp uygulamasını / belgelerini artırmalısınız. Umarım bu, RAII'yi anlamanıza yardımcı olur. Hakkında daha fazla bilgi edinmek için Andrei Alexandrescu tarafından hazırlanan Modern C ++ programlamasına başvurmalı veya kitaplığın (www.boostorg) shared_ptr.hpp uygulamasını / belgelerini artırmalısınız. Umarım bu, RAII'yi anlamanıza yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.