Ada perbedaan penting antara keduanya.
Segala sesuatu yang tidak dialokasikan dengan new
berperilaku seperti tipe nilai dalam C # (dan orang sering mengatakan bahwa objek-objek tersebut dialokasikan pada stack, yang mungkin merupakan kasus paling umum / jelas, tetapi tidak selalu benar. Lebih tepatnya, objek yang dialokasikan tanpa menggunakan new
memiliki penyimpanan otomatis Durasi
Segala sesuatu yang dialokasikan dengan new
dialokasikan pada heap, dan pointer ke sana dikembalikan, persis seperti tipe referensi di C #.
Apa pun yang dialokasikan pada tumpukan harus memiliki ukuran konstan, ditentukan pada waktu kompilasi (kompiler harus mengatur penunjuk tumpukan dengan benar, atau jika objek adalah anggota kelas lain, ia harus menyesuaikan ukuran kelas lain itu) . Itu sebabnya array dalam C # adalah tipe referensi. Mereka harus, karena dengan tipe referensi, kita dapat memutuskan pada saat runtime berapa banyak memori yang diminta. Dan hal yang sama berlaku di sini. Hanya array dengan ukuran konstan (ukuran yang dapat ditentukan pada waktu kompilasi) yang dapat dialokasikan dengan durasi penyimpanan otomatis (pada stack). Array berukuran dinamis harus dialokasikan di heap, dengan memanggil new
.
(Dan di situlah kesamaan dengan C # berhenti)
Sekarang, apa pun yang dialokasikan pada tumpukan memiliki durasi penyimpanan "otomatis" (Anda sebenarnya dapat mendeklarasikan variabel sebagai auto
, tetapi ini adalah default jika tidak ada jenis penyimpanan lain yang ditentukan sehingga kata kunci tidak benar-benar digunakan dalam praktiknya, tetapi di sinilah sebenarnya datang dari)
Durasi penyimpanan otomatis artinya persis seperti apa, durasi variabel ditangani secara otomatis. Sebaliknya, apa pun yang dialokasikan pada heap harus dihapus secara manual oleh Anda. Ini sebuah contoh:
void foo() {
bar b;
bar* b2 = new bar();
}
Fungsi ini menciptakan tiga nilai yang layak dipertimbangkan:
Pada baris 1, ia mendeklarasikan variabel b
tipe bar
pada stack (durasi otomatis).
Pada baris 2, ia menyatakan bar
pointer b2
pada stack (durasi otomatis), dan memanggil yang baru, mengalokasikan bar
objek pada heap. (durasi dinamis)
Ketika fungsi kembali, hal berikut akan terjadi: Pertama, b2
keluar dari ruang lingkup (urutan kehancuran selalu berlawanan dengan urutan konstruksi). Tapi b2
itu hanya sebuah pointer, jadi tidak ada yang terjadi, memori yang ditempatinya cukup dibebaskan. Dan yang terpenting, memori yang ditunjuknya ( bar
instance pada heap) TIDAK tersentuh. Hanya pointer yang dibebaskan, karena hanya pointer yang memiliki durasi otomatis. Kedua, b
keluar dari ruang lingkup, jadi karena memiliki durasi otomatis, penghancurnya disebut, dan memori dibebaskan.
Dan bar
contoh di heap? Mungkin masih di sana. Tidak ada yang mau repot menghapusnya, jadi udah bocor memori.
Dari contoh ini, kita dapat melihat bahwa apa pun dengan durasi otomatis dijamin memiliki destruktor yang dipanggil ketika keluar dari ruang lingkup. Itu berguna. Tetapi apa pun yang dialokasikan pada heap berlangsung selama kita membutuhkannya, dan dapat berukuran secara dinamis, seperti dalam kasus array. Itu juga berguna. Kita dapat menggunakannya untuk mengelola alokasi memori kita. Bagaimana jika kelas Foo mengalokasikan sebagian memori pada tumpukan di konstruktornya, dan menghapus memori itu di destruktornya. Kemudian kita bisa mendapatkan yang terbaik dari kedua dunia, alokasi memori yang aman yang dijamin akan dibebaskan lagi, tetapi tanpa batasan memaksa segalanya untuk berada di tumpukan.
Dan itu persis bagaimana sebagian besar kode C ++ bekerja. Lihatlah perpustakaan standar std::vector
misalnya. Itu biasanya dialokasikan pada stack, tetapi dapat secara dinamis berukuran dan diubah ukurannya. Dan ia melakukan ini dengan mengalokasikan memori pada tumpukan secara internal sesuai kebutuhan. Pengguna kelas tidak pernah melihat ini, jadi tidak ada kemungkinan kebocoran memori, atau lupa untuk membersihkan apa yang Anda alokasikan.
Prinsip ini disebut RAII (Akuisisi Sumber Daya adalah Inisialisasi), dan dapat diperluas ke sumber daya apa pun yang harus diperoleh dan dirilis. (soket jaringan, file, koneksi basis data, kunci sinkronisasi). Semuanya dapat diperoleh di konstruktor, dan dirilis di destruktor, sehingga Anda dijamin bahwa semua sumber daya yang Anda peroleh akan dibebaskan lagi.
Sebagai aturan umum, jangan pernah gunakan baru / hapus langsung dari kode tingkat tinggi Anda. Selalu bungkus dalam kelas yang dapat mengelola memori untuk Anda, dan yang akan memastikan itu dibebaskan lagi. (Ya, mungkin ada pengecualian untuk aturan ini. Khususnya, smart pointer mengharuskan Anda untuk memanggil new
langsung, dan meneruskan pointer ke konstruktornya, yang kemudian mengambil alih dan memastikan delete
dipanggil dengan benar. Tetapi ini masih merupakan aturan praktis yang sangat penting. )