Kapan saya harus menggunakan kata kunci baru di C ++?

273

Saya telah menggunakan C ++ untuk sementara waktu, dan saya bertanya-tanya tentang kata kunci baru . Cukup, haruskah saya menggunakannya, atau tidak?

1) Dengan kata kunci baru ...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";

2) Tanpa kata kunci baru ...

MyClass myClass;
myClass.MyField = "Hello world!";

Dari perspektif implementasi, mereka sepertinya tidak berbeda (tapi saya yakin begitu) ... Namun, bahasa utama saya adalah C #, dan tentu saja metode 1 adalah yang biasa saya gunakan.

Kesulitannya tampaknya bahwa metode 1 lebih sulit untuk digunakan dengan kelas C ++ Cd.

Metode mana yang harus saya gunakan?

Pembaruan 1:

Baru- baru ini saya menggunakan kata kunci baru untuk memori tumpukan (atau toko gratis ) untuk array besar yang keluar dari ruang lingkup (yaitu dikembalikan dari suatu fungsi). Di mana sebelumnya saya menggunakan tumpukan, yang menyebabkan setengah dari elemen menjadi korup di luar ruang lingkup, beralih ke tumpukan penggunaan memastikan bahwa elemen-elemen itu dalam kebijaksanaan. Yay!

Pembaruan 2:

Seorang teman saya baru-baru ini memberi tahu saya ada aturan sederhana untuk menggunakan newkata kunci; setiap kali Anda mengetik new, ketik delete.

Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

Ini membantu mencegah kebocoran memori, karena Anda selalu harus menghapusnya di suatu tempat (yaitu ketika Anda memotong dan menempelnya ke destruktor atau sebaliknya).

Nick Bolton
sumber
6
Jawaban singkatnya adalah, gunakan versi singkat ketika Anda bisa melakukannya. :)
jalf
11
Teknik yang lebih baik daripada selalu menulis penghapusan yang sesuai - gunakan wadah STL dan petunjuk pintar seperti std::vectordan std::shared_ptr. Ini membungkus panggilan ke newdan deleteuntuk Anda, sehingga Anda bahkan cenderung bocor memori. Tanyakan pada diri Anda, misalnya: apakah Anda selalu ingat untuk menempatkan korespondensi di deletemana - mana pengecualian dapat dilemparkan? Memasukkan deletes dengan tangan lebih sulit dari yang Anda kira.
AshleysBrain
@nbolton Re: PEMBARUAN 1 - Salah satu hal yang indah tentang C ++ adalah memungkinkan Anda untuk menyimpan Jenis yang Ditentukan Pengguna di tumpukan, sedangkan sampah yang dikumpulkan seperti C # memaksa Anda untuk menyimpan data di heap . Menyimpan data di heap menghabiskan lebih banyak sumber daya daripada menyimpan data di stack , jadi Anda harus memilih stack ke heap , kecuali ketika UDT Anda membutuhkan sejumlah besar memori untuk menyimpan datanya. (Ini juga berarti bahwa objek dilewatkan oleh nilai secara default). Solusi yang lebih baik untuk masalah Anda adalah meneruskan array ke fungsi dengan referensi .
Charles Addis

Jawaban:

304

Metode 1 (menggunakan new)

  • Mengalokasikan memori untuk objek di toko gratis (Ini sering kali sama dengan heap )
  • Mengharuskan Anda untuk secara eksplisit deleteobjek Anda nanti. (Jika Anda tidak menghapusnya, Anda dapat membuat kebocoran memori)
  • Memori tetap dialokasikan sampai Anda deletememilikinya. (yaitu Anda dapat returnmenggunakan objek yang Anda buat new)
  • Contoh dalam pertanyaan akan membocorkan memori kecuali penunjuknya adalah deleted; dan itu harus selalu dihapus , terlepas dari jalur kontrol mana yang diambil, atau jika ada pengecualian.

Metode 2 (tidak menggunakan new)

  • Mengalokasikan memori untuk objek pada stack (di mana semua variabel lokal pergi) Biasanya ada lebih sedikit memori yang tersedia untuk stack; jika Anda mengalokasikan terlalu banyak objek, Anda berisiko menumpuk berlebihan.
  • Anda tidak perlu melakukannya deletenanti.
  • Memori tidak lagi dialokasikan ketika keluar dari ruang lingkup. (mis. Anda seharusnya tidak returnmengarahkan pointer ke objek di stack)

Sejauh mana yang digunakan; Anda memilih metode yang paling sesuai untuk Anda, mengingat kendala di atas.

Beberapa kasus mudah:

  • Jika Anda tidak ingin khawatir tentang panggilan delete, (dan potensi menyebabkan kebocoran memori ) Anda tidak boleh menggunakannya new.
  • Jika Anda ingin mengembalikan pointer ke objek Anda dari suatu fungsi, Anda harus menggunakannya new
Daniel LeCheminant
sumber
4
Satu nitpick - saya percaya bahwa operator baru mengalokasikan memori dari "free store", sementara malloc mengalokasikan dari "heap". Ini tidak dijamin menjadi hal yang sama, meskipun dalam praktiknya biasanya demikian. Lihat gotw.ca/gotw/009.htm .
Fred Larson
4
Saya pikir jawaban Anda bisa lebih jelas untuk digunakan. (99% dari waktu, pilihannya sederhana. Gunakan metode 2, pada objek pembungkus yang memanggil baru / hapus dalam konstruktor / destruktor)
jalf
4
@jalf: Metode 2 adalah yang tidak menggunakan yang baru: - / Dalam kasus apa pun, ada beberapa kali kode Anda akan jauh lebih sederhana (misalnya menangani kasus kesalahan) menggunakan Metode 2 (yang tanpa yang baru)
Daniel LeCheminant
Nitpick lain ... Anda harus membuatnya lebih jelas bahwa contoh pertama Nick bocor memori, sedangkan yang kedua tidak, bahkan di hadapan pengecualian.
Arafangion
4
@Fred, Arafangion: Terima kasih atas wawasan Anda; Saya telah memasukkan komentar Anda ke dalam jawabannya.
Daniel LeCheminant
118

Ada perbedaan penting antara keduanya.

Segala sesuatu yang tidak dialokasikan dengan newberperilaku 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 newmemiliki penyimpanan otomatis Durasi Segala sesuatu yang dialokasikan dengan newdialokasikan 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 btipe barpada stack (durasi otomatis).

Pada baris 2, ia menyatakan barpointer b2pada stack (durasi otomatis), dan memanggil yang baru, mengalokasikan barobjek pada heap. (durasi dinamis)

Ketika fungsi kembali, hal berikut akan terjadi: Pertama, b2keluar dari ruang lingkup (urutan kehancuran selalu berlawanan dengan urutan konstruksi). Tapi b2itu hanya sebuah pointer, jadi tidak ada yang terjadi, memori yang ditempatinya cukup dibebaskan. Dan yang terpenting, memori yang ditunjuknya ( barinstance pada heap) TIDAK tersentuh. Hanya pointer yang dibebaskan, karena hanya pointer yang memiliki durasi otomatis. Kedua, bkeluar dari ruang lingkup, jadi karena memiliki durasi otomatis, penghancurnya disebut, dan memori dibebaskan.

Dan barcontoh 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::vectormisalnya. 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 newlangsung, dan meneruskan pointer ke konstruktornya, yang kemudian mengambil alih dan memastikan deletedipanggil dengan benar. Tetapi ini masih merupakan aturan praktis yang sangat penting. )

jalf
sumber
2
"Segala sesuatu yang tidak dialokasikan dengan yang baru ditempatkan pada tumpukan" Tidak dalam sistem yang telah saya kerjakan ... biasanya data global (statis) ditempatkan di segmen mereka sendiri. Misalnya, data, .bss, dll ... segmen tautan. Pedantic, aku tahu ...
Dan
Tentu saja kamu benar. Saya tidak benar-benar berpikir tentang data statis. Buruk saya, tentu saja. :)
jalf
2
Mengapa semua yang dialokasikan pada stack harus memiliki ukuran yang konstan?
user541686
Tidak selalu , ada beberapa cara untuk menghindarinya, tetapi dalam kasus umum, itu ada di tumpukan. Jika berada di bagian atas tumpukan, maka dimungkinkan untuk mengubah ukurannya, tetapi begitu ada sesuatu yang didorong di atasnya, itu "berdinding", dikelilingi oleh benda-benda di kedua sisi, sehingga tidak dapat benar-benar diubah ukurannya. . Ya, mengatakan bahwa itu selalu harus memiliki ukuran tetap adalah sedikit penyederhanaan, tapi itu menyampaikan ide dasar (dan saya tidak akan merekomendasikan bermain-main dengan fungsi C yang membuat Anda terlalu kreatif dengan alokasi stack)
jalf
14

Metode mana yang harus saya gunakan?

Ini hampir tidak pernah ditentukan oleh preferensi pengetikan Anda tetapi oleh konteksnya. Jika Anda perlu menyimpan objek di beberapa tumpukan atau jika terlalu berat untuk tumpukan Anda mengalokasikannya di toko gratis. Selain itu, karena Anda mengalokasikan objek, Anda juga bertanggung jawab untuk melepaskan memori. Cari deleteoperator.

Untuk meringankan beban menggunakan manajemen toko bebas orang-orang telah menemukan hal-hal seperti auto_ptrdan unique_ptr. Saya sangat menyarankan Anda melihat ini. Mereka bahkan mungkin bisa membantu masalah mengetik Anda ;-)

secara langsung
sumber
10

Jika Anda menulis dalam C ++ Anda mungkin menulis untuk kinerja. Menggunakan baru dan toko gratis jauh lebih lambat daripada menggunakan tumpukan (terutama ketika menggunakan utas) jadi hanya gunakan saat Anda membutuhkannya.

Seperti yang orang lain katakan, Anda perlu baru ketika objek Anda harus hidup di luar fungsi atau ruang lingkup objek, objek sangat besar atau ketika Anda tidak tahu ukuran array pada waktu kompilasi.

Juga, cobalah untuk tidak pernah menggunakan delete. Bungkus baru Anda menjadi pointer pintar sebagai gantinya. Biarkan panggilan pointer pintar hapus untuk Anda.

Ada beberapa kasus di mana pointer pintar tidak pintar. Jangan pernah menyimpan std :: auto_ptr <> di dalam wadah STL. Ini akan menghapus pointer terlalu cepat karena operasi penyalinan di dalam wadah. Kasus lain adalah ketika Anda memiliki wadah STL yang sangat besar dari pointer ke objek. boost :: shared_ptr <> akan memiliki satu ton overhead kecepatan saat menabrak jumlah referensi naik dan turun. Cara yang lebih baik untuk pergi dalam kasus itu adalah dengan menempatkan wadah STL ke objek lain dan memberikan objek itu destruktor yang akan memanggil delete pada setiap pointer dalam wadah.

Zan Lynx
sumber
10

Jawaban singkatnya adalah: jika Anda seorang pemula di C ++, Anda seharusnya tidak pernah menggunakan newatau deletediri Anda sendiri.

Sebagai gantinya, Anda harus menggunakan pointer pintar seperti std::unique_ptrdan std::make_unique(atau lebih jarang, std::shared_ptrdan std::make_shared). Dengan begitu, Anda tidak perlu khawatir tentang kebocoran memori. Dan bahkan jika Anda lebih maju, praktik terbaik biasanya adalah merangkum cara kustom yang Anda gunakan newdan deleteke dalam kelas kecil (seperti penunjuk cerdas khusus) yang didedikasikan hanya untuk mengatasi masalah siklus hidup.

Tentu saja, di belakang layar, pointer pintar ini masih melakukan alokasi dan deallokasi dinamis, sehingga kode yang menggunakannya masih memiliki overhead runtime terkait. Jawaban lain di sini telah membahas masalah ini, dan bagaimana membuat keputusan desain tentang kapan harus menggunakan smart pointer versus hanya membuat objek pada stack atau menggabungkannya sebagai anggota langsung dari suatu objek, cukup baik sehingga saya tidak akan mengulanginya. Tetapi ringkasan eksekutif saya adalah: jangan gunakan pointer pintar atau alokasi dinamis sampai sesuatu memaksa Anda melakukannya.

Daniel Schepler
sumber
menarik untuk melihat bagaimana jawaban dapat berubah seiring berjalannya waktu;)
Wolf
2

Tanpa newkata kunci Anda menyimpannya di tumpukan panggilan . Menyimpan variabel yang terlalu besar pada stack akan menyebabkan stack overflow .

vartec
sumber
2

Jawaban sederhananya adalah ya - new () membuat objek di heap (dengan efek samping yang tidak menguntungkan yang harus Anda kelola seumur hidup (dengan secara eksplisit memanggil delete di atasnya), sedangkan form kedua membuat objek di tumpukan saat ini lingkup dan objek itu akan hancur ketika keluar dari ruang lingkup.

Timo Geusch
sumber
1

Jika variabel Anda hanya digunakan dalam konteks fungsi tunggal, Anda lebih baik menggunakan variabel stack, yaitu, Opsi 2. Seperti yang orang lain katakan, Anda tidak perlu mengelola masa hidup variabel stack - mereka dibangun dan dihancurkan secara otomatis. Juga, mengalokasikan / membatalkan alokasi variabel pada heap lambat dengan perbandingan. Jika fungsi Anda dipanggil cukup sering, Anda akan melihat peningkatan kinerja yang luar biasa jika menggunakan variabel tumpukan versus variabel tumpukan.

Yang mengatakan, ada beberapa contoh yang jelas di mana variabel stack tidak cukup.

Jika variabel stack memiliki jejak memori yang besar, maka Anda berisiko meluap. Secara default, ukuran tumpukan setiap utas adalah 1 MB di Windows. Kecil kemungkinan bahwa Anda akan membuat variabel tumpukan yang berukuran 1 MB, tetapi Anda harus ingat bahwa pemanfaatan tumpukan bersifat kumulatif. Jika fungsi Anda memanggil fungsi yang memanggil fungsi lain yang memanggil fungsi lain yang ..., variabel stack di semua fungsi ini mengambil ruang di tumpukan yang sama. Fungsi rekursif dapat mengalami masalah ini dengan cepat, tergantung pada seberapa dalam rekursi tersebut. Jika ini merupakan masalah, Anda dapat menambah ukuran tumpukan (tidak disarankan) atau mengalokasikan variabel di tumpukan menggunakan operator baru (disarankan).

Yang lain, kondisi yang lebih mungkin adalah bahwa variabel Anda perlu "hidup" di luar ruang lingkup fungsi Anda. Dalam hal ini, Anda akan mengalokasikan variabel pada heap sehingga dapat dicapai di luar ruang lingkup fungsi yang diberikan.

Matt Davis
sumber
1

Apakah Anda mengeluarkan myClass dari suatu fungsi, atau mengharapkannya ada di luar fungsi itu? Seperti beberapa orang lain katakan, ini semua tentang ruang lingkup ketika Anda tidak mengalokasikan pada heap. Ketika Anda meninggalkan fungsi, itu hilang (akhirnya). Salah satu kesalahan klasik yang dibuat oleh pemula adalah upaya untuk membuat objek lokal beberapa kelas dalam suatu fungsi dan mengembalikannya tanpa mengalokasikannya di heap. Saya dapat mengingat debugging hal ini kembali di hari-hari awal saya melakukan c ++.

itsmatt
sumber
0

Metode kedua membuat instance pada stack, bersama dengan hal-hal seperti sesuatu yang dideklarasikan intdan daftar parameter yang dilewatkan ke fungsi.

Metode pertama memberi ruang bagi penunjuk pada tumpukan, yang telah Anda tetapkan ke lokasi di memori tempat yang baru MyClassdialokasikan di heap - atau toko gratis.

Metode pertama juga mengharuskan deleteAnda membuat apa yang Anda buat new, sedangkan dalam metode kedua, kelas secara otomatis dihancurkan dan dibebaskan ketika jatuh keluar dari ruang lingkup (biasanya kurung tutup berikutnya).

greyfade
sumber
-1

Jawaban singkatnya adalah ya kata kunci "baru" itu sangat penting karena ketika Anda menggunakannya, data objek disimpan di tumpukan sebagai lawan dari tumpukan, yang paling penting!

RAGNO
sumber