Memahami arti istilah dan konsep - RAII (Resource Acquisition is Initialization)

110

Bisakah Anda pengembang C ++ memberi kami penjelasan yang baik tentang apa itu RAII, mengapa itu penting, dan apakah itu mungkin memiliki relevansi dengan bahasa lain atau tidak?

Saya lakukan tahu sedikit. Saya yakin itu singkatan dari "Resource Acquisition is Initialization". Namun, nama itu tidak cocok dengan pemahaman saya (mungkin salah) tentang apa itu RAII: Saya mendapat kesan bahwa RAII adalah cara menginisialisasi objek di tumpukan sedemikian rupa sehingga, ketika variabel-variabel itu keluar dari ruang lingkup, destruktor akan secara otomatis disebut menyebabkan sumber daya dibersihkan.

Jadi mengapa itu tidak disebut "menggunakan tumpukan untuk memicu pembersihan" (UTSTTC :)? Bagaimana Anda pergi dari sana ke "RAII"?

Dan bagaimana Anda bisa membuat sesuatu di tumpukan yang akan menyebabkan pembersihan sesuatu yang hidup di tumpukan? Juga, adakah kasus di mana Anda tidak dapat menggunakan RAII? Apakah Anda pernah berharap untuk mengumpulkan sampah? Setidaknya pengumpul sampah yang dapat Anda gunakan untuk beberapa objek sambil membiarkan yang lain dikelola?

Terima kasih.

Charlie Flowers
sumber
27
UTSTTC? Saya suka itu! Ini jauh lebih intuitif daripada RAII. RAII adalah buruk bernama, aku ragu setiap C ++ programmer akan membantah itu. Tapi tidak mudah untuk berubah. ;)
jalf
10
Berikut pandangan Stroustrup tentang masalah tersebut: groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi
3
@sbi: Ngomong-ngomong, beri +1 pada komentar Anda hanya untuk penelitian sejarah. Saya percaya memiliki sudut pandang penulis (B. Stroustrup) tentang nama konsep (RAII) cukup menarik untuk memiliki jawabannya sendiri.
paercebal
1
@paercebal: Penelitian sejarah? Sekarang Anda telah membuat saya merasa sangat tua. :(Saya sedang membaca seluruh utas, saat itu, dan bahkan tidak menganggap diri saya seorang pemula C ++!
sbi
3
+1, Saya akan mengajukan pertanyaan yang sama, senang saya bukan satu-satunya yang memahami konsep tetapi tidak memahami namanya. Sepertinya itu seharusnya disebut RAOI - Resource Acquisition On Initialization.
laurent

Jawaban:

132

Jadi mengapa itu tidak disebut "menggunakan tumpukan untuk memicu pembersihan" (UTSTTC :)?

RAII memberi tahu Anda apa yang harus dilakukan: Dapatkan sumber daya Anda di konstruktor! Saya akan menambahkan: satu sumber daya, satu konstruktor. UTSTTC hanyalah salah satu aplikasi dari itu, RAII lebih dari itu.

Manajemen Sumber Daya menyebalkan. Di sini, sumber daya adalah segala sesuatu yang perlu dibersihkan setelah digunakan. Studi proyek di banyak platform menunjukkan sebagian besar bug terkait dengan manajemen sumber daya - dan ini sangat buruk di Windows (karena banyak jenis objek dan pengalokasi).

Di C ++, pengelolaan sumber daya menjadi sangat rumit karena kombinasi pengecualian dan template (gaya C ++). Untuk mengintip di bawah tenda, lihat GOTW8 ).


C ++ menjamin bahwa destruktor dipanggil jika dan hanya jika konstruktor berhasil. Mengandalkan itu, RAII dapat memecahkan banyak masalah buruk yang mungkin tidak disadari oleh programmer rata-rata. Berikut adalah beberapa contoh di luar "variabel lokal saya akan dihancurkan setiap kali saya kembali".

Mari kita mulai dengan FileHandlekelas yang terlalu sederhana yang menggunakan RAII:

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;
    }
}

Jika konstruksi gagal (dengan pengecualian), tidak ada fungsi anggota lain - bahkan destruktornya - yang dipanggil.

RAII menghindari penggunaan objek dalam keadaan tidak valid. itu sudah membuat hidup lebih mudah bahkan sebelum kita menggunakan objeknya.

Sekarang, mari kita lihat objek sementara:

void CopyFileData(FileHandle source, FileHandle dest);

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

Ada tiga kasus kesalahan yang harus ditangani: tidak ada file yang dapat dibuka, hanya satu file yang dapat dibuka, kedua file dapat dibuka tetapi penyalinan file gagal. Dalam implementasi non-RAII, Fooharus menangani ketiga kasus secara eksplisit.

RAII melepaskan sumber daya yang diperoleh, meskipun beberapa sumber daya diperoleh dalam satu pernyataan.

Sekarang, mari kita menggabungkan beberapa objek:

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!";
    }
}

Konstruktor Loggerakan gagal jika originalkonstruktor gagal (karena filename1tidak dapat dibuka), duplexkonstruktor gagal (karena filename2tidak dapat dibuka), atau penulisan ke file di dalam Loggerbadan konstruktor gagal. Dalam salah satu kasus ini, Loggerdestruktor tidak akan dipanggil - jadi kita tidak bisa mengandalkan Loggerdestruktor untuk melepaskan file. Tetapi jika originaldibangun, destruktornya akan dipanggil selama pembersihan Loggerkonstruktor.

RAII menyederhanakan pembersihan setelah konstruksi parsial.


Poin negatif:

Poin negatif? Semua masalah dapat diselesaikan dengan RAII dan petunjuk cerdas ;-)

RAII terkadang sulit digunakan saat Anda membutuhkan akuisisi tertunda, yang mendorong objek gabungan ke heap.
Bayangkan Logger membutuhkan a SetTargetFile(const char* target). Dalam hal ini, pegangan, yang masih perlu menjadi anggota Logger, harus berada di heap (misalnya dalam penunjuk cerdas, untuk memicu penghancuran pegangan dengan tepat.)

Saya tidak pernah berharap untuk mengumpulkan sampah juga. Ketika saya melakukan C # saya terkadang merasakan momen kebahagiaan karena saya tidak perlu peduli, tetapi lebih dari itu saya merindukan semua mainan keren yang dapat diciptakan melalui kehancuran deterministik. (menggunakan IDisposablesaja tidak memotongnya.)

Saya memiliki satu struktur yang sangat kompleks yang mungkin mendapat manfaat dari GC, di mana petunjuk cerdas "sederhana" akan menyebabkan referensi melingkar di beberapa kelas. Kami menyelesaikannya dengan hati-hati menyeimbangkan petunjuk yang kuat dan yang lemah, tetapi kapan pun kami ingin mengubah sesuatu, kami harus mempelajari bagan hubungan yang besar. GC mungkin lebih baik, tetapi beberapa komponen memiliki sumber daya yang seharusnya dirilis secepatnya.


Catatan pada sampel FileHandle: Itu tidak dimaksudkan sebagai lengkap, hanya sampel - tetapi ternyata salah. Terima kasih Johannes Schaub untuk menunjukkan dan FredOverflow untuk mengubahnya menjadi solusi C ++ 0x yang benar. Seiring waktu, saya telah menyelesaikan pendekatan yang didokumentasikan di sini .

peterchen
sumber
1
1 Untuk menunjukkan bahwa GC dan ASAP tidak bertautan. Tidak sering sakit tetapi jika tidak mudah didiagnosis: /
Matthieu M.
10
Satu kalimat khususnya yang saya abaikan pada bacaan sebelumnya. Anda mengatakan bahwa "RAII" memberi tahu Anda, "Dapatkan sumber daya Anda di dalam konstruktor." Itu masuk akal dan hampir merupakan parafrase kata demi kata dari "RAII". Sekarang saya membuatnya lebih baik (saya akan memilih Anda lagi jika saya bisa :)
Charlie Flowers
2
Satu keuntungan utama dari GC adalah bahwa kerangka alokasi memori dapat mencegah pembuatan referensi yang menjuntai tanpa adanya kode "tidak aman" (jika kode "tidak aman" diizinkan, tentu saja, kerangka kerja tidak dapat mencegah apa pun). GC juga sering unggul RAII ketika berhadapan dengan shared berubah benda seperti string yang sering tidak memiliki pemilik yang jelas dan tidak memerlukan pembersihan. Sangat disayangkan bahwa lebih banyak kerangka kerja tidak berusaha menggabungkan GC dan RAII, karena sebagian besar aplikasi akan memiliki campuran objek yang tidak dapat diubah (di mana GC akan menjadi yang terbaik) dan objek yang perlu dibersihkan (di mana RAII terbaik).
supercat
@supercat: Saya biasanya suka GC - tapi hanya bekerja untuk sumber daya yang "mengerti" GC. Misalnya .NET GC tidak mengetahui biaya objek COM. Ketika hanya membuat dan menghancurkannya dalam satu loop, itu akan dengan senang hati membiarkan aplikasi berjalan ke tanah terkait ruang alamat atau memori virtual - apa pun yang lebih dulu - bahkan tanpa memikirkan mungkin melakukan GC. --- selanjutnya, bahkan dalam lingkungan GC yang sempurna, saya masih merindukan kekuatan pemusnahan deterministik: Anda dapat menerapkan pola yang sama ke artificats lain, misalnya menampilkan elemen UI dalam kondisi tertentu.
peterchen
@peterchen: Satu hal yang menurut saya tidak ada dalam banyak pemikiran terkait OOP adalah konsep kepemilikan objek. Melacak kepemilikan sering kali jelas diperlukan untuk objek dengan sumber daya, tetapi juga sering diperlukan untuk objek yang dapat berubah tanpa sumber daya. Secara umum, objek harus merangkum statusnya yang dapat berubah baik dalam referensi ke objek permanen yang mungkin dibagikan, atau dalam objek yang dapat berubah yang menjadi pemilik eksklusifnya. Kepemilikan eksklusif seperti itu tidak selalu menyiratkan akses tulis eksklusif, tetapi jika Foomemiliki Bar, dan Bozmemutasinya, ...
supercat
42

Ada jawaban yang sangat bagus di luar sana, jadi saya hanya menambahkan beberapa hal yang terlupakan.

0. RAII adalah tentang ruang lingkup

RAII adalah tentang keduanya:

  1. memperoleh sumber daya (tidak peduli sumber daya apa) di konstruktor, dan membatalkan memperolehnya di destruktor.
  2. membuat konstruktor dieksekusi ketika variabel dideklarasikan, dan destruktor secara otomatis dieksekusi ketika variabel keluar dari ruang lingkup.

Yang lain sudah menjawab tentang itu, jadi saya tidak akan menjelaskannya.

1. Saat melakukan coding di Java atau C #, Anda sudah menggunakan RAII ...

MONSIEUR JOURDAIN: Apa! Ketika saya berkata, "Nicole, bawakan sandal saya, dan berikan saya minuman malam saya," itu prosa?

FILOSOFI MASTER: Ya, Pak.

MONSIEUR JOURDAIN: Selama lebih dari empat puluh tahun saya telah berbicara prosa tanpa mengetahui apa pun tentangnya, dan saya sangat berterima kasih kepada Anda karena telah mengajari saya hal itu.

- Molière: Pria Kelas Menengah, Babak 2, Adegan 4

Seperti yang dilakukan Monsieur Jourdain dengan prosa, C # dan bahkan orang Java sudah menggunakan RAII, tetapi dengan cara tersembunyi. Misalnya, kode Java berikut (yang ditulis dengan cara yang sama di C # dengan menggantinya synchronizeddengan lock):

void foo()
{
   // etc.

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

   // etc.
}

... sudah menggunakan RAII: Akuisisi mutex dilakukan di kata kunci ( synchronizedatau lock), dan pembatalan akuisisi akan dilakukan saat keluar dari cakupan.

Sangat alami dalam notasinya sehingga hampir tidak memerlukan penjelasan bahkan bagi orang yang belum pernah mendengar tentang RAII.

Keunggulan C ++ dibandingkan Java dan C # di sini adalah bahwa apa pun dapat dibuat menggunakan RAII. Misalnya, tidak ada build-in langsung yang setara dengan synchronizednor lockdi C ++, tetapi kita masih bisa memilikinya.

Di C ++, itu akan ditulis:

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.
}

yang dapat dengan mudah ditulis dengan cara Java / C # (menggunakan makro C ++):

void foo()
{
   // etc.

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

   // etc.
}

2. RAII memiliki kegunaan alternatif

WHITE RABBIT: [bernyanyi] Saya terlambat / Saya terlambat / Untuk kencan yang sangat penting. / Tidak ada waktu untuk mengatakan "Halo." / Selamat tinggal. / Saya terlambat, saya terlambat, saya terlambat.

- Alice in Wonderland (versi Disney, 1951)

Anda tahu kapan konstruktor akan dipanggil (di deklarasi objek), dan Anda tahu kapan destruktor yang sesuai akan dipanggil (di pintu keluar cakupan), sehingga Anda dapat menulis kode yang hampir ajaib dengan hanya satu baris. Selamat datang di negeri ajaib C ++ (setidaknya, dari sudut pandang pengembang C ++).

Misalnya, Anda dapat menulis objek counter (saya biarkan sebagai latihan) dan menggunakannya hanya dengan mendeklarasikan variabelnya, seperti objek kunci di atas digunakan:

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
}

yang tentu saja, dapat ditulis, sekali lagi, cara Java / C # menggunakan makro:

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. Mengapa C ++ kurang finally?

[BERTERIAK] Ini hitungan mundur terakhir !

- Eropa: The Final Countdown (maaf, saya kehabisan kutipan, di sini ... :-)

The finallyklausul digunakan dalam C # / Java untuk menangani pembuangan sumber daya dalam kasus lingkup keluar (baik melalui returnatau pengecualian dilemparkan).

Pembaca spesifikasi yang cerdik akan menyadari bahwa C ++ tidak memiliki klausa akhirnya. Dan ini bukan kesalahan, karena C ++ tidak membutuhkannya, karena RAII sudah menangani pembuangan sumber daya. (Dan percayalah, menulis destruktor C ++ jauh lebih mudah daripada menulis klausa akhir Java yang benar, atau bahkan metode Buang C # yang benar).

Meski begitu, terkadang, finallyklausul akan tetap keren. Bisakah kita melakukannya di C ++? Ya kita bisa! Dan lagi dengan penggunaan RAII secara bergantian.

Kesimpulan: RAII lebih dari sekedar filosofi dalam C ++: Ini C ++

RAII? INI ADALAH C ++ !!!

- Komentar marah pengembang C ++, tanpa malu-malu disalin oleh raja Sparta yang tidak dikenal dan 300 temannya

Ketika Anda mencapai beberapa tingkat pengalaman dalam C ++, Anda mulai berpikir dalam istilah RAII , dalam istilah eksekusi otomatis konstruktor dan destruktor .

Anda mulai berpikir dalam lingkup cakupan , {dan }karakter dan menjadi salah satu yang paling penting dalam kode Anda.

Dan hampir semuanya cocok dalam hal RAII: pengecualian keamanan, mutex, koneksi database, permintaan database, koneksi server, jam, pegangan OS, dll., Dan terakhir, namun tidak kalah pentingnya, memori.

Bagian database tidak dapat diabaikan, karena, jika Anda menerima pembayaran, Anda bahkan dapat menulis dalam gaya " pemrograman transaksional ", mengeksekusi baris dan baris kode sampai memutuskan, pada akhirnya, jika Anda ingin melakukan semua perubahan , atau, jika tidak memungkinkan, mengembalikan semua perubahan (selama setiap baris memenuhi setidaknya Jaminan Pengecualian Kuat). (lihat bagian kedua dari artikel Herb's Sutter ini untuk pemrograman transaksional).

Dan seperti teka-teki, semuanya cocok.

RAII adalah bagian dari C ++, C ++ tidak bisa menjadi C ++ tanpanya.

Ini menjelaskan mengapa developer C ++ berpengalaman begitu terpikat dengan RAII, dan mengapa RAII menjadi hal pertama yang mereka telusuri saat mencoba bahasa lain.

Dan ini menjelaskan mengapa Pengumpul Sampah, meskipun merupakan bagian dari teknologi yang luar biasa, tidak begitu mengesankan dari sudut pandang pengembang C ++:

  • RAII sudah menangani sebagian besar kasus yang ditangani oleh GC
  • GC menangani lebih baik daripada RAII dengan referensi melingkar pada objek terkelola murni (dimitigasi dengan penggunaan cerdas pointer lemah)
  • Tetap A GC terbatas pada memori, sementara RAII dapat menangani semua jenis sumber daya.
  • Seperti dijelaskan di atas, RAII dapat melakukan lebih banyak lagi ...
paercebal.dll
sumber
Penggemar Java: Menurut saya, GC jauh lebih berguna daripada RAII karena menangani semua memori dan membebaskan Anda dari banyak potensi bug. Dengan GC, Anda dapat membuat referensi melingkar, mengembalikan dan menyimpan referensi dan sulit untuk salah (menyimpan referensi ke objek yang seharusnya berumur pendek memperpanjang waktu aktifnya, yang merupakan semacam kebocoran memori, tetapi itulah satu-satunya masalah) . Menangani sumber daya dengan GC tidak berfungsi, tetapi sebagian besar sumber daya dalam aplikasi memiliki siklus langsung yang sepele, dan beberapa yang tersisa bukanlah masalah besar. Saya berharap kami dapat memiliki GC dan RAII, tetapi itu tampaknya tidak mungkin.
maaartinus
16

Silahkan lihat:

Apakah pemrogram bahasa lain, selain C ++, menggunakan, mengetahui, atau memahami RAII?

RAII dan smart pointer dalam C ++

Apakah C ++ mendukung blok 'akhirnya'? (Dan apa itu 'RAII' yang terus saya dengar?)

RAII vs. pengecualian

dll ..

Mitch Wheat
sumber
1
Beberapa di antaranya benar sesuai dengan pertanyaan saya, tetapi pencarian tidak menemukan mereka, begitu pula daftar "pertanyaan terkait" yang muncul setelah Anda memasukkan pertanyaan baru. Terima kasih untuk tautannya.
Charlie Flowers
1
@Charlie: Build in search sangat lemah dalam beberapa hal. Menggunakan sintaks tag ("[topic]") sangat membantu, dan banyak orang menggunakan google ...
dmckee --- mantan moderator kucing
10

RAII menggunakan semantik destruktor C ++ untuk mengelola sumber daya. Misalnya, pertimbangkan penunjuk cerdas. Anda memiliki konstruktor berparameter dari pointer yang menginisialisasi pointer ini dengan alamat objek. Anda mengalokasikan pointer pada tumpukan:

SmartPointer pointer( new ObjectClass() );

Ketika smart pointer keluar dari ruang lingkup destruktor dari kelas pointer menghapus objek yang terhubung. Pointer dialokasikan stack dan objek - dialokasikan heap.

Ada kasus-kasus tertentu ketika RAII tidak membantu. Misalnya, jika Anda menggunakan petunjuk cerdas penghitungan referensi (seperti boost :: shared_ptr) dan membuat struktur seperti grafik dengan siklus, Anda berisiko mengalami kebocoran memori karena objek dalam siklus akan mencegah satu sama lain untuk dilepaskan. Pengumpulan sampah akan membantu melawan ini.

gigi tajam
sumber
2
Jadi harus disebut UCDSTMR :)
Daniel Daranas
Setelah dipikir-pikir, saya pikir UDSTMR lebih tepat. Bahasa (C ++) diberikan, jadi huruf "C" tidak diperlukan dalam akronim. UDSTMR adalah singkatan dari Using Destructor Semantics To Manage Resources.
Daniel Daranas
9

Saya ingin menjelaskannya sedikit lebih kuat dari tanggapan sebelumnya.

RAII, Resource Acquisition Is Initialization berarti bahwa semua sumber daya yang diperoleh harus diperoleh dalam konteks inisialisasi suatu objek. Ini melarang akuisisi sumber daya secara "telanjang". Alasannya adalah bahwa pembersihan di C ++ bekerja pada basis objek, bukan basis pemanggilan fungsi. Karenanya, semua pembersihan harus dilakukan oleh objek, bukan pemanggilan fungsi. Dalam pengertian ini C ++ lebih berorientasi objek daripada misalnya Java. Pembersihan Java didasarkan pada pemanggilan fungsi dalam finallyklausa.

MSalters
sumber
Jawaban yang bagus. Dan "inisialisasi suatu objek" berarti "konstruktor", ya?
Charlie Flowers
@Charlie: ya, terutama dalam kasus ini.
MSalters
8

Saya setuju dengan cpitis. Tetapi ingin menambahkan bahwa sumber daya dapat berupa apa saja, bukan hanya memori. Sumber daya bisa berupa file, bagian penting, utas atau koneksi database.

Ini disebut Resource Acquisition Is Initialization karena sumber daya diperoleh ketika objek yang mengontrol sumber daya dibuat, Jika konstruktor gagal (yaitu karena pengecualian) sumber daya tidak diperoleh. Kemudian setelah objek keluar dari ruang lingkup, sumber daya dilepaskan. c ++ menjamin bahwa semua objek di tumpukan yang telah berhasil dibangun akan dihancurkan (ini termasuk konstruktor kelas dasar dan anggota bahkan jika konstruktor kelas super gagal).

Alasan di balik RAII adalah untuk membuat pengecualian akuisisi sumber daya aman. Bahwa semua sumber daya yang diperoleh dilepaskan dengan benar di mana pun pengecualian terjadi. Namun ini bergantung pada kualitas kelas yang memperoleh sumber daya (ini harus pengecualian aman dan ini sulit).

iain
sumber
Luar biasa, terima kasih telah menjelaskan alasan di balik nama tersebut. Seperti yang saya pahami, Anda mungkin memparafrasekan RAII sebagai, "Jangan pernah memperoleh sumber daya melalui mekanisme lain selain inisialisasi (berbasis konstruktor)". Iya?
Charlie Flowers
Ya, ini adalah kebijakan saya, namun saya sangat berhati-hati dalam menulis kelas RAII saya sendiri karena harus dikecualikan aman. Ketika saya menulisnya, saya mencoba memastikan keamanan pengecualian dengan menggunakan kembali kelas RAII lain yang ditulis oleh para ahli.
iain
Saya tidak menganggapnya sulit untuk ditulis. Jika kelas Anda cukup kecil, itu tidak sulit sama sekali.
Rob K
7

Masalah dengan pengumpulan sampah adalah Anda kehilangan kehancuran deterministik yang penting bagi RAII. Setelah variabel keluar dari ruang lingkup, terserah pengumpul sampah kapan objek akan diklaim kembali. Sumber daya yang dipegang oleh objek akan terus ditahan hingga destruktor dipanggil.

Mark Ransom
sumber
4
Masalahnya bukan hanya determinisme. Masalah sebenarnya adalah bahwa finalisator (penamaan java) menghalangi GC. GC efisien karena tidak mengingat objek mati, tetapi mengabaikannya hingga terlupakan. GC harus melacak objek dengan finalizer dengan cara yang berbeda untuk menjamin bahwa objek tersebut dipanggil
David Rodríguez - dribeas
1
kecuali di java / c # Anda mungkin akan membersihkan di blok terakhir daripada di finalizer.
jk.
4

RAII berasal dari Resource Allocation Is Initialization. Pada dasarnya, ini berarti bahwa ketika konstruktor menyelesaikan eksekusi, objek yang dibangun sepenuhnya diinisialisasi dan siap digunakan. Ini juga menyiratkan bahwa destruktor akan melepaskan sumber daya apa pun (misalnya memori, sumber daya OS) yang dimiliki oleh objek.

Dibandingkan dengan bahasa / teknologi yang dikumpulkan sampah (misalnya Java, .NET), C ++ memungkinkan kontrol penuh atas kehidupan suatu objek. Untuk objek yang dialokasikan tumpukan, Anda akan tahu kapan destruktor objek akan dipanggil (saat eksekusi keluar dari cakupan), hal yang tidak benar-benar dikontrol dalam kasus pengumpulan sampah. Bahkan menggunakan smart pointer di C ++ (mis. Boost :: shared_ptr), Anda akan tahu bahwa jika tidak ada referensi ke objek yang dituju, destruktor dari objek itu akan dipanggil.

Cătălin Pitiș
sumber
3

Dan bagaimana Anda bisa membuat sesuatu di tumpukan yang akan menyebabkan pembersihan sesuatu yang hidup di tumpukan?

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.

Ketika sebuah instance dari int_buffer muncul, ia harus memiliki ukuran, dan itu akan mengalokasikan memori yang diperlukan. Ketika keluar dari ruang lingkup, itu destruktor yang dipanggil. Ini sangat berguna untuk hal-hal seperti objek sinkronisasi. Mempertimbangkan

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.

Juga, adakah kasus di mana Anda tidak dapat menggunakan RAII?

Tidak terlalu.

Apakah Anda pernah berharap untuk mengumpulkan sampah? Setidaknya pengumpul sampah yang dapat Anda gunakan untuk beberapa objek sambil membiarkan yang lain dikelola?

Tidak pernah. Pengumpulan sampah hanya menyelesaikan sebagian kecil dari pengelolaan sumber daya dinamis.

Rob K
sumber
Saya telah menggunakan Java dan C # sangat sedikit, jadi saya tidak pernah melewatkannya, tetapi GC jelas membatasi gaya saya dalam hal manajemen sumber daya ketika saya harus menggunakannya, karena saya tidak dapat menggunakan RAII.
Rob K
1
Saya telah banyak menggunakan C # dan setuju dengan Anda 100%. Faktanya, saya menganggap GC non-deterministik sebagai kewajiban dalam suatu bahasa.
Nemanja Trifunovic
2

Sudah ada banyak jawaban bagus di sini, tetapi saya hanya ingin menambahkan:
Penjelasan sederhana tentang RAII adalah bahwa, dalam C ++, objek yang dialokasikan di tumpukan akan dimusnahkan setiap kali berada di luar cakupan. Itu berarti, perusak objek akan dipanggil dan dapat melakukan semua pembersihan yang diperlukan.
Artinya, jika sebuah objek dibuat tanpa "baru", tidak diperlukan "penghapusan". Dan ini juga merupakan ide di balik "penunjuk cerdas" - mereka berada di tumpukan, dan pada dasarnya membungkus objek berbasis tumpukan.

E Dominique
sumber
1
Tidak, mereka tidak melakukannya. Tapi apakah Anda punya alasan kuat untuk pernah membuat penunjuk cerdas di heap? Omong-omong, penunjuk cerdas hanyalah contoh di mana RAII dapat berguna.
E Dominique
1
Mungkin penggunaan "stack" vs "heap" saya sedikit ceroboh - yang dimaksud dengan objek di "tumpukan" adalah objek lokal. Secara alami dapat menjadi bagian dari objek misalnya di heap. Dengan "membuat smart pointer di heap", saya bermaksud menggunakan new / delete pada smart pointer itu sendiri.
E Dominique
1

RAII adalah singkatan dari Resource Acquisition Is Initialization.

Teknik ini sangat unik untuk C ++ karena dukungan mereka untuk Constructors & Destructors & hampir secara otomatis konstruktor yang cocok dengan argumen yang diteruskan atau dalam kasus terburuk konstruktor default disebut & destructors jika penjelasan yang diberikan disebut sebaliknya yang default yang ditambahkan oleh compiler C ++ dipanggil jika Anda tidak menulis destruktor secara eksplisit untuk kelas C ++. Ini hanya terjadi untuk objek C ++ yang dikelola secara otomatis - artinya tidak menggunakan penyimpanan gratis (memori dialokasikan / dialokasikan menggunakan operator baru [] / delete, delete [] C ++ baru).

Teknik RAII memanfaatkan fitur objek yang dikelola otomatis ini untuk menangani objek yang dibuat di heap / free-store dengan secara eksplisit meminta lebih banyak memori menggunakan new / new [], yang harus dimusnahkan secara eksplisit dengan memanggil delete / delete [] . Kelas objek yang dikelola otomatis akan membungkus objek lain yang dibuat pada memori heap / penyimpanan bebas ini. Oleh karena itu, ketika konstruktor objek yang dikelola otomatis dijalankan, objek yang dibungkus dibuat pada memori heap / free-store & saat pegangan objek yang dikelola otomatis keluar dari ruang lingkup, destruktor dari objek yang dikelola otomatis itu dipanggil secara otomatis di mana objek dihancurkan menggunakan delete. Dengan konsep OOP, jika Anda membungkus objek tersebut di dalam kelas lain dalam lingkup privat, Anda tidak akan memiliki akses ke kelas yang dibungkus anggota & metode & inilah alasan mengapa smart pointer (alias menangani kelas) dirancang untuk. Pointer cerdas ini mengekspos objek yang dibungkus sebagai objek yang diketik ke dunia luar & di sana dengan memungkinkan untuk memanggil anggota / metode apa pun yang terdiri dari objek memori yang terekspos. Perhatikan bahwa petunjuk cerdas memiliki berbagai rasa berdasarkan kebutuhan yang berbeda. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII. Anda harus mengacu pada pemrograman C ++ Modern oleh Andrei Alexandrescu atau meningkatkan implementasi / dokumentasi shared_ptr.hpp library (www.boostorg) untuk mempelajari lebih lanjut tentangnya. Semoga ini bisa membantu Anda untuk memahami RAII.

ahli teknologi
sumber