Yang dimaksud dengan Akuisisi Sumber Daya adalah Inisialisasi (RAII)?

Jawaban:

374

Ini adalah nama yang sangat mengerikan untuk konsep yang sangat kuat, dan mungkin salah satu hal nomor 1 yang dilewatkan oleh pengembang C ++ ketika mereka beralih ke bahasa lain. Ada sedikit gerakan untuk mencoba mengubah nama konsep ini sebagai Manajemen Sumber Daya Lingkup-Bound , meskipun tampaknya belum berhasil.

Ketika kita mengatakan 'Sumber Daya' kita tidak hanya berarti memori - itu bisa berupa pegangan file, soket jaringan, pegangan basis data, objek GDI ... Singkatnya, hal-hal yang memiliki persediaan terbatas dan oleh karena itu kita harus dapat kendalikan penggunaannya. Aspek 'Lingkup-terikat' berarti bahwa masa objek terikat pada ruang lingkup variabel, jadi ketika variabel keluar dari ruang lingkup maka destruktor akan melepaskan sumber daya. Properti yang sangat berguna dari ini adalah membuat pengecualian keamanan yang lebih besar. Sebagai contoh, bandingkan ini:

RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation();  // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks

Dengan RAII

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

Dalam kasus yang terakhir ini, ketika pengecualian dilemparkan dan tumpukan dibatalkan, variabel lokal dihancurkan yang memastikan bahwa sumber daya kita dibersihkan dan tidak bocor.

the_mandrill
sumber
2
@the_mandrill: Saya mencoba ideone.com/1Jjzuc program ini. Tetapi tidak ada panggilan destruktor. The tomdalling.com/blog/software-design/… mengatakan bahwa C ++ menjamin bahwa destruktor objek pada stack akan dipanggil, bahkan jika ada pengecualian. Jadi, mengapa destruktor tidak mengeksekusi di sini? Apakah sumber daya saya bocor atau tidak akan pernah dibebaskan atau dilepaskan?
Destructor
8
Pengecualian dilemparkan, tetapi Anda tidak menangkapnya, sehingga aplikasi berakhir. Jika Anda membungkus dengan try {} catch () {} maka bekerja seperti yang diharapkan: ideone.com/xm2GR9
the_mandrill
2
Tidak yakin apakah Scope-Boundpilihan nama terbaik di sini karena penspesifikasi kelas penyimpanan bersama dengan ruang lingkup menentukan durasi penyimpanan suatu entitas. Mempersempit batasan ruang lingkup mungkin merupakan penyederhanaan yang berguna, namun tidak 100% tepat
SebNag
125

Ini adalah idiom pemrograman yang secara singkat berarti bahwa Anda

  • merangkum sumber daya ke dalam kelas (yang konstruktornya biasanya - tetapi tidak selalu ** - memperoleh sumber daya, dan destruktornya selalu melepaskannya)
  • gunakan sumber daya melalui instance lokal dari kelas *
  • sumber daya secara otomatis dibebaskan ketika objek keluar dari ruang lingkup

Ini menjamin bahwa apa pun yang terjadi ketika sumber daya sedang digunakan, pada akhirnya akan dibebaskan (apakah karena pengembalian normal, penghancuran objek yang mengandung, atau pengecualian yang dilemparkan).

Ini adalah praktik yang baik digunakan secara luas dalam C ++, karena selain sebagai cara yang aman untuk menangani sumber daya, ini juga membuat kode Anda jauh lebih bersih karena Anda tidak perlu mencampur kode penanganan kesalahan dengan fungsi utama.

* Pembaruan: "lokal" dapat berarti variabel lokal, atau variabel anggota tidak statis dari suatu kelas. Dalam kasus terakhir variabel anggota diinisialisasi dan dihancurkan dengan objek pemiliknya.

** Update2: seperti yang ditunjukkan oleh @sbi, sumber daya - meskipun sering dialokasikan di dalam konstruktor - juga dapat dialokasikan di luar dan diteruskan sebagai parameter.

Péter Török
sumber
1
AFAIK, akronim tidak menyiratkan objek harus pada variabel (tumpukan) lokal. Itu bisa menjadi variabel anggota objek lain, jadi ketika objek 'holding' dihancurkan, objek anggota juga hancur, dan sumber daya dilepaskan. Bahkan, saya pikir akronim secara khusus hanya berarti bahwa tidak ada open()/ close()metode untuk menginisialisasi dan melepaskan sumber daya, hanya konstruktor dan destruktor, sehingga 'memegang' sumber daya hanya seumur hidup objek, tidak peduli apakah itu seumur hidup adalah ditangani oleh konteks (tumpukan) atau secara eksplisit (alokasi dinamis)
Javier
1
Sebenarnya tidak ada yang mengatakan sumber daya harus diperoleh di konstruktor. Mengarsipkan stream, string dan wadah lain melakukan itu, tetapi sumber daya mungkin juga diteruskan ke konstruktor, seperti yang biasanya terjadi dengan smart pointer. Karena jawaban Anda adalah yang paling banyak dipilih, Anda mungkin ingin memperbaikinya.
sbi
Ini bukan akronim, ini adalah singkatan. IIRC kebanyakan orang mengucapkannya "ar ey ay ay" sehingga tidak benar-benar memenuhi syarat untuk akronim seperti katakanlah DARPA, yang diucapkan DARPA bukannya dieja. Juga, saya akan mengatakan RAII adalah sebuah paradigma daripada idiom belaka.
dtech
@ Peter Torok: Saya mencoba ideone.com/1Jjzuc program ini. Tetapi tidak ada panggilan destruktor. The tomdalling.com/blog/software-design/… mengatakan bahwa C ++ menjamin bahwa destruktor objek pada stack akan dipanggil, bahkan jika ada pengecualian. Jadi, mengapa destruktor tidak mengeksekusi di sini? Apakah sumber daya saya bocor atau tidak akan pernah dibebaskan atau dilepaskan?
Destructor
50

"RAII" adalah singkatan dari "Akuisisi Sumber Daya adalah Inisialisasi" dan sebenarnya cukup keliru, karena ini bukan akuisisi sumber daya (dan inisialisasi objek) yang bersangkutan, tetapi melepaskan sumber daya (dengan cara penghancuran objek) ).
Tapi RAII adalah nama yang kita dapat dan tongkat itu.

Pada intinya, idiom ini menampilkan sumber daya enkapsulasi (potongan memori, file terbuka, mutex yang tidak terkunci, Anda-nama-itu) di objek lokal, otomatis , dan memiliki penghancur objek yang melepaskan sumber daya ketika objek dihancurkan di akhir dari ruang lingkup miliknya:

{
  raii obj(acquire_resource());
  // ...
} // obj's dtor will call release_resource()

Tentu saja, objek tidak selalu objek lokal, otomatis. Mereka juga bisa menjadi anggota kelas:

class something {
private:
  raii obj_;  // will live and die with instances of the class
  // ... 
};

Jika objek tersebut mengelola memori, mereka sering disebut "smart pointer".

Ada banyak variasi dari ini. Misalnya, dalam cuplikan kode pertama, muncul pertanyaan apa yang akan terjadi jika seseorang ingin menyalin obj. Cara termudah adalah dengan tidak mengizinkan penyalinan. std::unique_ptr<>, penunjuk pintar untuk menjadi bagian dari pustaka standar seperti yang ditampilkan oleh standar C ++ berikutnya, melakukan hal ini.
Penunjuk pintar lainnya, std::shared_ptrfitur "kepemilikan bersama" dari sumber daya (objek yang dialokasikan secara dinamis) yang dimilikinya. Artinya, itu dapat dengan bebas disalin dan semua salinan merujuk ke objek yang sama. Pointer pintar melacak berapa banyak salinan merujuk ke objek yang sama dan akan menghapusnya ketika yang terakhir sedang dihancurkan.
Varian ketiga ditampilkan olehstd::auto_ptr yang mengimplementasikan semacam semantik bergerak: Suatu objek hanya dimiliki oleh satu pointer, dan upaya untuk menyalin objek akan menghasilkan (melalui peretasan sintaksis) dalam mentransfer kepemilikan objek ke target operasi penyalinan.

sbi
sumber
4
std::auto_ptradalah versi usang dari std::unique_ptr. std::auto_ptrjenis semantik langkah disimulasikan sebanyak mungkin dalam C ++ 98, std::unique_ptrmenggunakan semantik langkah baru C ++ 11. Kelas baru dibuat karena semantik langkah C ++ 11 lebih eksplisit (memerlukan std::movekecuali dari sementara) sementara itu default untuk setiap salinan dari non-const di std::auto_ptr.
Jan Hudec
@JiahaoCai: Suatu kali, bertahun-tahun yang lalu (di Usenet), Stroustrup sendiri mengatakan demikian.
sbi
21

Seumur hidup suatu objek ditentukan oleh cakupannya. Namun, terkadang kita perlu, atau berguna, untuk membuat objek yang hidup secara independen dari ruang lingkup di mana ia dibuat. Dalam C ++, operator newdigunakan untuk membuat objek seperti itu. Dan untuk menghancurkan objek, operator deletedapat digunakan. Objek yang dibuat oleh operator newdialokasikan secara dinamis, yaitu dialokasikan dalam memori dinamis (juga disebut heap atau free store ). Jadi, objek yang dibuat oleh newakan terus ada sampai secara eksplisit dihancurkan menggunakan delete.

Beberapa kesalahan yang dapat terjadi saat menggunakan newdan deleteadalah:

  • Objek bocor (atau memori): gunakan newuntuk mengalokasikan objek dan lupa deleteobjek.
  • Hapus prematur (atau referensi menggantung ): memegang pointer lain ke suatu objek, deleteobjek, dan kemudian menggunakan pointer lainnya.
  • Hapus ganda : mencoba deleteobjek dua kali.

Secara umum, variabel cakupan lebih disukai. Namun, RAII dapat digunakan sebagai alternatif newdan deleteuntuk membuat objek hidup secara independen dari ruang lingkupnya. Teknik semacam itu terdiri dari mengambil pointer ke objek yang dialokasikan pada heap dan menempatkannya di objek handle / manager . Yang terakhir memiliki destruktor yang akan mengurus penghancuran objek. Ini akan menjamin bahwa objek tersedia untuk fungsi apa pun yang ingin mengaksesnya, dan bahwa objek dihancurkan ketika masa pakai objek pegangan berakhir, tanpa perlu pembersihan eksplisit.

Contoh dari pustaka standar C ++ yang menggunakan RAII adalah std::stringdan std::vector.

Pertimbangkan potongan kode ini:

void fn(const std::string& str)
{
    std::vector<char> vec;
    for (auto c : str)
        vec.push_back(c);
    // do something
}

ketika Anda membuat vektor dan Anda mendorong elemen ke sana, Anda tidak peduli tentang mengalokasikan dan membatalkan alokasi elemen tersebut. Vektor digunakan newuntuk mengalokasikan ruang untuk elemen-elemennya di heap, dan deleteuntuk membebaskan ruang itu. Anda sebagai pengguna vektor, Anda tidak peduli tentang detail implementasi dan akan mempercayai vektor untuk tidak bocor. Dalam hal ini, vektor adalah objek pegangan dari elemen-elemennya.

Contoh lain dari perpustakaan standar yang digunakan RAII adalah std::shared_ptr, std::unique_ptr, dan std::lock_guard.

Nama lain untuk teknik ini adalah SBRM , kependekan dari Scope-Bound Resource Management .

Elmiomar
sumber
1
"SBRM" jauh lebih masuk akal bagi saya. Saya sampai pada pertanyaan ini karena saya pikir saya mengerti RAII tetapi nama itu membuat saya kesal, mendengarnya menggambarkannya sebagai "Manajemen Sumberdaya Lingkup-Terikat" membuat saya langsung menyadari bahwa saya memang memahami konsep itu.
JShorthouse
Saya tidak yakin mengapa ini tidak menandai ini sebagai jawaban untuk pertanyaan itu. Ini adalah jawaban yang sangat teliti dan ditulis dengan baik, terima kasih @elmiomar
Abdelrahman Shoman
13

Buku C ++ Pemrograman dengan Pola Desain Terungkap menggambarkan RAII sebagai:

  1. Memperoleh semua sumber daya
  2. Menggunakan sumber daya
  3. Melepaskan sumber daya

Dimana

  • Sumberdaya diimplementasikan sebagai kelas, dan semua pointer memiliki pembungkus kelas di sekitar mereka (menjadikannya pointer cerdas).

  • Sumber daya diperoleh dengan memanggil konstruktor mereka dan dirilis secara implisit (dalam urutan terbalik dari perolehan) dengan memanggil destruktor mereka.

Dennis
sumber
1
@Brandin Saya telah mengedit posting saya sehingga pembaca akan fokus pada konten yang penting, daripada memperdebatkan wilayah abu-abu hukum hak cipta tentang apa yang merupakan penggunaan yang adil.
Dennis
7

Ada tiga bagian untuk kelas RAII:

  1. Sumber daya dilepaskan dalam destruktor
  2. Contoh kelas dialokasikan stack
  3. Sumber daya diperoleh dalam konstruktor. Bagian ini opsional, tetapi umum.

RAII singkatan dari "Akuisisi Sumber Daya adalah inisialisasi." Bagian "akuisisi sumber daya" dari RAII adalah tempat Anda memulai sesuatu yang harus diakhiri nanti, seperti:

  1. Membuka file
  2. Mengalokasikan sebagian memori
  3. Mendapatkan kunci

Bagian "is inisialisasi" berarti bahwa akuisisi terjadi di dalam konstruktor kelas.

https://www.tomdalling.com/blog/software-design/resource-acquis-is-initialisation-raii-explained/

Mohammad Moridi
sumber
5

Manajemen memori manual adalah mimpi buruk yang programmer temukan cara untuk menghindari sejak penemuan kompiler. Memprogram bahasa dengan pemulung membuat hidup lebih mudah, tetapi dengan mengorbankan kinerja. Dalam artikel ini - Menghilangkan Pengumpul Sampah: Cara RAII , insinyur Toptal Peter Goodspeed-Niklaus memberi kita mengintip sejarah pemulung dan menjelaskan bagaimana pengertian kepemilikan dan pinjaman dapat membantu menghilangkan pengumpul sampah tanpa mengorbankan jaminan keselamatan mereka.

Dmitry Pavlov
sumber