Perlu dicatat bahwa, dalam kasus C ++, kesalahpahaman umum bahwa "Anda perlu melakukan manajemen memori manual". Bahkan, Anda biasanya tidak melakukan manajemen memori dalam kode Anda.
Objek ukuran tetap (dengan masa hidup lingkup)
Dalam sebagian besar kasus ketika Anda membutuhkan objek, objek tersebut akan memiliki masa hidup yang ditentukan dalam program Anda dan dibuat di stack. Ini berfungsi untuk semua tipe data primitif bawaan, tetapi juga untuk instance kelas dan struct:
class MyObject {
public: int x;
};
int objTest()
{
MyObject obj;
obj.x = 5;
return obj.x;
}
Tumpukan objek secara otomatis dihapus ketika fungsi berakhir. Di Jawa, objek selalu dibuat di heap, dan karena itu harus dihapus oleh beberapa mekanisme seperti pengumpulan sampah. Ini bukan masalah untuk objek tumpukan.
Objek yang mengelola data dinamis (dengan cakupan masa pakai)
Menggunakan ruang pada tumpukan berfungsi untuk objek dengan ukuran tetap. Ketika Anda membutuhkan jumlah ruang variabel, seperti array, pendekatan lain digunakan: Daftar ini diringkas dalam objek ukuran tetap yang mengelola memori dinamis untuk Anda. Ini berfungsi karena objek dapat memiliki fungsi pembersihan khusus, penghancur. Dijamin akan dipanggil ketika objek keluar dari ruang lingkup dan melakukan kebalikan dari konstruktor:
class MyList {
public:
// a fixed-size pointer to the actual memory.
int* listOfInts;
// constructor: get memory
MyList(size_t numElements) { listOfInts = new int[numElements]; }
// destructor: free memory
~MyList() { delete[] listOfInts; }
};
int listTest()
{
MyList list(1024);
list.listOfInts[200] = 5;
return list.listOfInts[200];
// When MyList goes off stack here, its destructor is called and frees the memory.
}
Tidak ada manajemen memori sama sekali dalam kode di mana memori digunakan. Satu-satunya hal yang perlu kita pastikan adalah bahwa objek yang kita tulis memiliki destruktor yang sesuai. Tidak peduli bagaimana kita meninggalkan ruang lingkup listTest
, baik itu melalui pengecualian atau hanya dengan kembali dari itu, destruktor ~MyList()
akan dipanggil dan kita tidak perlu mengelola memori apa pun.
(Saya pikir ini adalah keputusan desain yang lucu untuk menggunakan operator biner BUKAN~
,, untuk menunjukkan destruktor. Ketika digunakan pada angka, itu membalikkan bit; dalam analogi, ini menunjukkan bahwa apa yang dilakukan konstruktor terbalik.)
Pada dasarnya semua objek C ++ yang membutuhkan memori dinamis menggunakan enkapsulasi ini. Itu telah disebut RAII ("akuisisi sumber daya adalah inisialisasi"), yang merupakan cara yang cukup aneh untuk mengekspresikan ide sederhana bahwa objek peduli dengan konten mereka sendiri; apa yang mereka peroleh adalah milik mereka untuk dibersihkan.
Objek polimorfik dan masa hidup di luar ruang lingkup
Sekarang, kedua kasus ini untuk memori yang memiliki masa pakai yang jelas: Masa pakai sama dengan ruang lingkup. Jika kita tidak ingin objek kedaluwarsa saat kita meninggalkan ruang lingkup, ada mekanisme ketiga yang dapat mengatur memori untuk kita: pointer cerdas. Pointer pintar juga digunakan ketika Anda memiliki instance objek yang tipenya bervariasi saat runtime, tetapi yang memiliki antarmuka umum atau kelas dasar:
class MyDerivedObject : public MyObject {
public: int y;
};
std::unique_ptr<MyObject> createObject()
{
// actually creates an object of a derived class,
// but the user doesn't need to know this.
return std::make_unique<MyDerivedObject>();
}
int dynamicObjTest()
{
std::unique_ptr<MyObject> obj = createObject();
obj->x = 5;
return obj->x;
// At scope end, the unique_ptr automatically removes the object it contains,
// calling its destructor if it has one.
}
Ada jenis lain dari smart pointer std::shared_ptr
,, untuk berbagi objek di antara beberapa klien. Mereka hanya menghapus objek yang terkandung ketika klien terakhir keluar dari ruang lingkup, sehingga mereka dapat digunakan dalam situasi di mana sama sekali tidak diketahui berapa banyak klien akan ada dan berapa lama mereka akan menggunakan objek.
Singkatnya, kami melihat bahwa Anda tidak benar-benar melakukan manajemen memori manual. Semuanya dienkapsulasi dan kemudian dirawat dengan cara yang sepenuhnya otomatis, manajemen memori berbasis ruang lingkup. Dalam kasus di mana ini tidak cukup, pointer pintar digunakan yang merangkum memori mentah.
Ini dianggap praktik yang sangat buruk untuk menggunakan pointer mentah sebagai pemilik sumber daya di mana saja dalam kode C ++, alokasi mentah di luar konstruktor, dan delete
panggilan mentah di luar destruktor, karena mereka hampir mustahil untuk dikelola ketika pengecualian terjadi, dan umumnya sulit digunakan dengan aman.
Yang terbaik: ini bekerja untuk semua jenis sumber daya
Salah satu manfaat terbesar RAII adalah tidak terbatas pada memori. Ini sebenarnya menyediakan cara yang sangat alami untuk mengelola sumber daya seperti file dan soket (membuka / menutup) dan mekanisme sinkronisasi seperti mutex (mengunci / membuka kunci). Pada dasarnya, setiap sumber daya yang dapat diperoleh dan harus dirilis dikelola dengan cara yang persis sama di C ++, dan tidak satu pun dari manajemen ini diserahkan kepada pengguna. Itu semua dirangkum dalam kelas yang memperoleh di konstruktor dan rilis di destruktor.
Misalnya, fungsi mengunci mutex biasanya ditulis seperti ini di C ++:
void criticalSection() {
std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
doSynchronizedStuff();
} // myMutex is released here automatically
Bahasa lain membuat ini jauh lebih rumit, baik dengan mengharuskan Anda melakukan ini secara manual (misalnya dalam finally
klausa) atau mereka menelurkan mekanisme khusus yang memecahkan masalah ini, tetapi tidak dengan cara yang sangat elegan (biasanya nanti dalam hidup mereka, ketika cukup banyak orang memiliki menderita karena kekurangannya). Mekanisme seperti ini adalah coba-dengan-sumber daya di Jawa dan pernyataan penggunaan dalam C #, yang keduanya merupakan perkiraan RAII C ++.
Jadi, singkatnya, semua ini adalah akun RAII yang sangat dangkal di C ++, tapi saya harap ini membantu pembaca untuk memahami bahwa memori dan bahkan manajemen sumber daya di C ++ biasanya tidak "manual", tetapi sebenarnya kebanyakan otomatis.