Saya berasal dari latar belakang Java dan sudah mulai bekerja dengan objek di C ++. Tetapi satu hal yang terpikir oleh saya adalah bahwa orang sering menggunakan pointer ke objek daripada objek itu sendiri, misalnya deklarasi ini:
Object *myObject = new Object;
daripada:
Object myObject;
Atau alih-alih menggunakan fungsi, katakanlah testFunc()
, seperti ini:
myObject.testFunc();
kita harus menulis:
myObject->testFunc();
Tetapi saya tidak tahu mengapa kita harus melakukannya dengan cara ini. Saya akan menganggap itu ada hubungannya dengan efisiensi dan kecepatan karena kita mendapatkan akses langsung ke alamat memori. Apakah saya benar?
Jawaban:
Sangat disayangkan bahwa Anda melihat alokasi dinamis begitu sering. Itu hanya menunjukkan berapa banyak programmer C ++ yang buruk.
Dalam arti tertentu, Anda memiliki dua pertanyaan yang digabungkan menjadi satu. Yang pertama adalah kapan kita harus menggunakan alokasi dinamis (menggunakan
new
)? Yang kedua adalah kapan kita harus menggunakan pointer?Pesan bawa pulang yang penting adalah bahwa Anda harus selalu menggunakan alat yang sesuai untuk pekerjaan itu . Di hampir semua situasi, ada sesuatu yang lebih tepat dan lebih aman daripada melakukan alokasi dinamis manual dan / atau menggunakan pointer mentah.
Alokasi dinamis
Dalam pertanyaan Anda, Anda telah menunjukkan dua cara membuat objek. Perbedaan utama adalah durasi penyimpanan objek. Saat melakukan
Object myObject;
dalam blok, objek dibuat dengan durasi penyimpanan otomatis, yang berarti akan dihancurkan secara otomatis ketika keluar dari ruang lingkup. Ketika Anda melakukannyanew Object()
, objek memiliki durasi penyimpanan dinamis, yang berarti ia tetap hidup sampai Anda secara eksplisitdelete
. Anda hanya harus menggunakan durasi penyimpanan dinamis saat Anda membutuhkannya. Artinya, Anda harus selalu lebih suka membuat objek dengan durasi penyimpanan otomatis ketika Anda bisa .Dua situasi utama di mana Anda mungkin memerlukan alokasi dinamis:
Ketika Anda benar-benar membutuhkan alokasi dinamis, Anda harus merangkumnya dalam smart pointer atau jenis lain yang melakukan RAII (seperti wadah standar). Pointer pintar memberikan semantik kepemilikan objek yang dialokasikan secara dinamis. Lihatlah
std::unique_ptr
danstd::shared_ptr
, misalnya. Jika Anda menggunakannya dengan benar, Anda hampir sepenuhnya dapat menghindari melakukan manajemen memori Anda sendiri (lihat Aturan Nol ).Pointer
Namun, ada kegunaan lain yang lebih umum untuk pointer mentah di luar alokasi dinamis, tetapi sebagian besar memiliki alternatif yang harus Anda pilih. Seperti sebelumnya, selalu lebih suka alternatif kecuali Anda benar-benar membutuhkan petunjuk .
Anda perlu referensi semantik . Terkadang Anda ingin meneruskan objek menggunakan pointer (terlepas dari bagaimana itu dialokasikan) karena Anda ingin fungsi yang Anda lewati memiliki akses ke objek tertentu (bukan salinannya). Namun, dalam sebagian besar situasi, Anda harus lebih memilih tipe referensi daripada pointer, karena ini memang khusus dirancang untuk mereka. Perhatikan ini tidak selalu tentang memperpanjang umur objek di luar cakupan saat ini, seperti dalam situasi 1 di atas. Seperti sebelumnya, jika Anda baik-baik saja dengan mengirimkan salinan objek, Anda tidak perlu semantik referensi.
Anda perlu polimorfisme . Anda hanya dapat memanggil fungsi secara polimorfik (yaitu, sesuai dengan tipe dinamis suatu objek) melalui penunjuk atau referensi ke objek. Jika itu perilaku yang Anda butuhkan, maka Anda perlu menggunakan pointer atau referensi. Sekali lagi, referensi harus lebih disukai.
Anda ingin menyatakan bahwa suatu objek adalah opsional dengan membiarkan a
nullptr
diteruskan ketika objek tersebut dihilangkan. Jika ini argumen, Anda sebaiknya menggunakan argumen default atau fungsi yang berlebihan. Jika tidak, Anda sebaiknya menggunakan tipe yang merangkum perilaku ini, sepertistd::optional
(diperkenalkan dalam C ++ 17 - dengan standar C ++ sebelumnya, gunakanboost::optional
).Anda ingin memisahkan unit kompilasi untuk meningkatkan waktu kompilasi . Properti berguna dari pointer adalah bahwa Anda hanya memerlukan deklarasi maju dari tipe menunjuk-ke (untuk benar-benar menggunakan objek, Anda akan memerlukan definisi). Ini memungkinkan Anda untuk memisahkan bagian-bagian dari proses kompilasi Anda, yang secara signifikan dapat meningkatkan waktu kompilasi. Lihat idiom Pimpl .
Anda perlu berinteraksi dengan perpustakaan C atau perpustakaan gaya-C. Pada titik ini, Anda terpaksa menggunakan pointer mentah. Hal terbaik yang dapat Anda lakukan adalah memastikan Anda hanya membiarkan pointer mentah Anda lepas pada saat-saat terakhir. Anda bisa mendapatkan pointer mentah dari pointer cerdas, misalnya, dengan menggunakan
get
fungsi anggotanya. Jika pustaka melakukan beberapa alokasi untuk Anda yang ia harapkan akan Anda alokasikan melalui pegangan, Anda sering dapat membungkus gagang di pointer cerdas dengan deleter kustom yang akan membatalkan alokasi objek dengan tepat.sumber
Object myObject(param1, etc...)
Ada banyak kasus penggunaan untuk pointer.
Perilaku polimorfik . Untuk tipe polimorfik, pointer (atau referensi) digunakan untuk menghindari pemotongan:
Referensi semantik dan menghindari penyalinan . Untuk jenis non-polimorfik, penunjuk (atau referensi) akan menghindari menyalin objek yang berpotensi mahal
Perhatikan bahwa C ++ 11 telah memindahkan semantik yang dapat menghindari banyak salinan objek mahal ke dalam argumen fungsi dan sebagai nilai pengembalian. Tetapi menggunakan pointer pasti akan menghindari itu dan akan memungkinkan beberapa pointer pada objek yang sama (sedangkan objek hanya dapat dipindahkan dari satu kali).
Akuisisi sumber daya . Membuat pointer ke sumber daya menggunakan
new
operator adalah anti-pola dalam C ++ modern. Gunakan kelas sumber daya khusus (salah satu wadah Standar) atau penunjuk pintar (std::unique_ptr<>
ataustd::shared_ptr<>
). Mempertimbangkan:vs.
Pointer mentah hanya boleh digunakan sebagai "tampilan" dan tidak dengan cara apa pun terlibat dalam kepemilikan, baik melalui penciptaan langsung atau secara implisit melalui nilai pengembalian. Lihat juga Tanya Jawab ini dari C ++ FAQ .
Kontrol seumur hidup yang lebih halus Setiap kali pointer bersama disalin (misalnya sebagai argumen fungsi) sumber daya yang ditunjukkannya tetap hidup. Objek reguler (tidak dibuat oleh
new
, baik secara langsung oleh Anda atau di dalam kelas sumber daya) dihancurkan ketika keluar dari ruang lingkup.sumber
unique_ptr
/ pindah semantikhun(b)
juga membutuhkan pengetahuan tentang tanda tangan kecuali Anda baik-baik saja dengan tidak tahu bahwa Anda memasukkan jenis yang salah sampai kompilasi. Meskipun masalah referensi biasanya tidak akan tertangkap pada waktu kompilasi dan akan membutuhkan lebih banyak upaya untuk debug, jika Anda memeriksa tanda tangan untuk memastikan argumennya benar, Anda juga akan dapat melihat apakah ada argumen yang menjadi referensi jadi bit referensi menjadi sesuatu yang bukan masalah (terutama ketika menggunakan IDE atau editor teks yang menunjukkan tanda tangan dari fungsi yang dipilih). Jugaconst&
,.Ada banyak jawaban bagus untuk pertanyaan ini, termasuk kasus penggunaan penting deklarasi maju, polimorfisme dll. Tapi saya merasa bagian dari "jiwa" pertanyaan Anda tidak dijawab - yaitu apa arti sintaks yang berbeda di seluruh Jawa dan C ++.
Mari kita periksa situasi membandingkan kedua bahasa:
Jawa:
Setara terdekat dengan ini, adalah:
C ++:
Mari kita lihat cara C ++ alternatif:
Cara terbaik untuk memikirkannya adalah bahwa - lebih atau kurang - Java (secara implisit) menangani pointer ke objek, sedangkan C ++ dapat menangani pointer ke objek, atau objek itu sendiri. Ada pengecualian untuk ini - misalnya, jika Anda mendeklarasikan tipe Java "primitif", mereka adalah nilai aktual yang disalin, dan bukan pointer. Begitu,
Jawa:
Yang mengatakan, menggunakan pointer TIDAK selalu benar atau cara yang salah untuk menangani sesuatu; Namun jawaban lain telah mencakup hal itu dengan memuaskan. Namun gagasan umum adalah bahwa dalam C ++ Anda memiliki kontrol lebih besar pada masa hidup objek, dan di mana mereka akan hidup.
Ambil titik asal -
Object * object = new Object()
konstruknya adalah semantik yang paling dekat dengan tipikal Java (atau C #).sumber
Object2 is now "dead"
: Saya pikir maksud AndamyObject1
atau lebih tepatnyathe object pointed to by myObject1
.Object object1 = new Object(); Object object2 = new Object();
adalah kode yang sangat buruk. Baru atau kedua Obyek konstruktor kedua dapat melempar, dan sekarang objek1 bocor. Jika Anda menggunakannew
s mentah , Anda harus membungkusnew
objek ed dalam pembungkus RAII secepatnya.Alasan bagus lainnya untuk menggunakan pointer adalah untuk deklarasi maju . Dalam proyek yang cukup besar mereka benar-benar dapat mempercepat waktu kompilasi.
sumber
std::unique_ptr<T>
berfungsi dengan deklarasi maju dariT
. Anda hanya perlu memastikan bahwa ketika destructor daristd::unique_ptr<T>
disebut,T
adalah tipe yang lengkap. Ini biasanya berarti kelas Anda yang berisistd::unique_ptr<T>
mendeklarasikan destruktornya di file header dan mengimplementasikannya dalam file cpp (bahkan jika implementasinya kosong).Kata pengantar
Java tidak seperti C ++, bertentangan dengan hype. Mesin Java hype ingin Anda percaya bahwa karena Java memiliki sintaksis C ++, maka bahasanya mirip. Tidak ada yang bisa lebih jauh dari kebenaran. Informasi yang salah ini adalah bagian dari alasan mengapa programmer Java pergi ke C ++ dan menggunakan sintaks seperti Java tanpa memahami implikasi dari kode mereka.
Selanjutnya kita pergi
Sebaliknya, sebenarnya. Tumpukan jauh lebih lambat daripada tumpukan, karena tumpukan sangat sederhana dibandingkan dengan tumpukan. Variabel penyimpanan otomatis (alias tumpukan variabel) memiliki destruktor mereka dipanggil begitu mereka keluar dari ruang lingkup. Sebagai contoh:
Di sisi lain, jika Anda menggunakan pointer yang dialokasikan secara dinamis, penghancurnya harus dipanggil secara manual.
delete
memanggil destruktor ini untuk Anda.Ini tidak ada hubungannya dengan
new
sintaks yang lazim di C # dan Java. Mereka digunakan untuk tujuan yang sama sekali berbeda.Manfaat alokasi dinamis
Salah satu masalah pertama yang dialami oleh banyak programmer C ++ adalah ketika mereka menerima input sewenang-wenang dari pengguna, Anda hanya dapat mengalokasikan ukuran tetap untuk variabel stack. Anda juga tidak dapat mengubah ukuran array. Sebagai contoh:
Tentu saja, jika Anda menggunakan
std::string
gantinya, secarastd::string
internal mengubah ukuran sendiri sehingga seharusnya tidak menjadi masalah. Tetapi pada dasarnya solusi untuk masalah ini adalah alokasi dinamis. Anda dapat mengalokasikan memori dinamis berdasarkan input pengguna, misalnya:Karena tumpukan jauh lebih besar dari tumpukan, seseorang dapat secara sewenang-wenang mengalokasikan / mengalokasikan kembali memori sebanyak yang dia butuhkan, sedangkan tumpukan memiliki batasan.
Bagaimana ini manfaat yang Anda minta? Jawabannya akan menjadi jelas setelah Anda memahami kebingungan / mitos di balik array dan pointer. Secara umum diasumsikan bahwa mereka sama, tetapi mereka tidak sama. Mitos ini berasal dari fakta bahwa pointer dapat disalin seperti halnya array dan karena peluruhan array ke pointer di tingkat atas dalam deklarasi fungsi. Namun, begitu sebuah array meluruh menjadi sebuah pointer, pointer kehilangan
sizeof
informasinya. Begitusizeof(pointer)
akan memberikan ukuran pointer dalam byte, yang biasanya 8 byte pada sistem 64-bit.Anda tidak dapat menetapkan untuk array, hanya menginisialisasi mereka. Sebagai contoh:
Di sisi lain, Anda dapat melakukan apa pun yang Anda inginkan dengan pointer. Sayangnya, karena perbedaan antara pointer dan array adalah lambaian tangan di Jawa dan C #, pemula tidak mengerti perbedaannya.
Java dan C # memiliki fasilitas yang memungkinkan Anda untuk memperlakukan objek sebagai objek lain, misalnya menggunakan
as
kata kunci. Jadi, jika seseorang ingin memperlakukanEntity
objek sebagaiPlayer
objek, orang dapat melakukannya.Player player = Entity as Player;
Ini sangat berguna jika Anda bermaksud memanggil fungsi pada wadah yang homogen yang seharusnya hanya berlaku untuk jenis tertentu. Fungsionalitas dapat dicapai dengan cara serupa di bawah ini:Jadi katakanlah jika hanya Segitiga yang memiliki fungsi Putar, itu akan menjadi kesalahan kompiler jika Anda mencoba menyebutnya pada semua objek kelas. Dengan menggunakan
dynamic_cast
, Anda dapat mensimulasikanas
kata kunci. Untuk menjadi jelas, jika gips gagal, itu mengembalikan pointer yang tidak valid. Jadi!test
pada dasarnya adalah singkatan untuk memeriksa apakahtest
NULL atau pointer tidak valid, yang berarti para pemain gagal.Manfaat variabel otomatis
Setelah melihat semua hal hebat yang dapat dilakukan alokasi dinamis, Anda mungkin bertanya-tanya mengapa tidak ada yang TIDAK menggunakan alokasi dinamis sepanjang waktu? Saya sudah memberi tahu Anda satu alasan, tumpukannya lambat. Dan jika Anda tidak membutuhkan semua memori itu, Anda tidak harus menyalahgunakannya. Jadi, inilah beberapa kelemahan tanpa urutan tertentu:
Ini rawan kesalahan. Alokasi memori manual berbahaya dan Anda cenderung bocor. Jika Anda tidak mahir menggunakan debugger atau
valgrind
(alat kebocoran memori), Anda dapat menarik rambut keluar dari kepala. Untungnya idiom dan petunjuk pintar RAII meringankan ini sedikit, tetapi Anda harus terbiasa dengan praktik seperti Aturan Tiga dan Aturan Lima. Ini adalah banyak informasi untuk diambil, dan pemula yang entah tidak tahu atau tidak peduli akan jatuh ke dalam perangkap ini.Itu tidak perlu. Tidak seperti Java dan C # yang menggunakan
new
kata kunci di mana-mana, di C ++, Anda hanya boleh menggunakannya jika perlu. Ungkapan umum berbunyi, semuanya tampak seperti paku jika Anda memiliki palu. Sedangkan pemula yang mulai dengan C ++ takut pointer dan belajar menggunakan variabel stack berdasarkan kebiasaan, Java dan programmer C # memulai dengan menggunakan pointer tanpa memahaminya! Itu benar-benar menginjak kaki yang salah. Anda harus meninggalkan semua yang Anda tahu karena sintaks adalah satu hal, belajar bahasa adalah hal lain.Salah satu optimisasi yang dilakukan oleh banyak kompiler adalah hal-hal yang disebut elision dan optimasi nilai pengembalian . Hal-hal ini dapat menghindarkan copys yang tidak perlu yang berguna untuk objek yang sangat besar, seperti vektor yang mengandung banyak elemen. Biasanya praktik umum adalah menggunakan pointer untuk mentransfer kepemilikan daripada menyalin objek besar untuk memindahkannya . Ini telah mengarah pada awal semantik langkah dan pointer pintar .
Jika Anda menggunakan pointer, (N) RVO TIDAK terjadi. Lebih menguntungkan dan lebih rentan kesalahan untuk memanfaatkan (N) RVO daripada mengembalikan atau melewati pointer jika Anda khawatir tentang pengoptimalan. Kebocoran kesalahan dapat terjadi jika pemanggil suatu fungsi bertanggung jawab atas
delete
objek yang dialokasikan secara dinamis dan semacamnya. Mungkin sulit untuk melacak kepemilikan suatu objek jika pointer dibagikan seperti kentang panas. Cukup gunakan variabel tumpukan karena lebih sederhana dan lebih baik.sumber
{ std::string* s = new std::string; } delete s; // destructor called
.... pasti inidelete
tidak akan berhasil karena kompiler tidak akan tahu apas
lagi?C ++ memberi Anda tiga cara untuk melewatkan objek: dengan pointer, dengan referensi, dan dengan nilai. Java membatasi Anda dengan yang terakhir (satu-satunya pengecualian adalah tipe primitif seperti int, boolean dll). Jika Anda ingin menggunakan C ++ bukan hanya seperti mainan aneh, maka Anda sebaiknya mengetahui perbedaan antara tiga cara ini.
Jawa berpura-pura bahwa tidak ada masalah seperti 'siapa dan kapan harus menghancurkan ini?'. Jawabannya adalah: Pengumpul Sampah, Hebat dan Mengerikan. Namun demikian, itu tidak dapat memberikan perlindungan 100% terhadap kebocoran memori (ya, java dapat membocorkan memori ). Sebenarnya, GC memberi Anda rasa aman yang salah. Semakin besar SUV Anda, semakin lama Anda menuju evacuator.
C ++ membuat Anda berhadapan muka dengan manajemen siklus hidup objek. Nah, ada cara untuk menghadapinya ( keluarga smart pointer , QObject di Qt dan sebagainya), tetapi tidak satu pun dari mereka dapat digunakan dengan cara 'api dan lupakan' seperti GC: Anda harus selalu mengingat penanganan memori. Anda tidak hanya harus peduli menghancurkan objek, Anda juga harus menghindari menghancurkan objek yang sama lebih dari satu kali.
Belum takut? Ok: referensi siklik - tangani sendiri, manusia. Dan ingat: bunuh setiap objek tepat sekali, kita runtime C ++ tidak suka mereka yang mengacaukan mayat, biarkan yang mati sendirian.
Jadi, kembali ke pertanyaan Anda.
Ketika Anda melewati objek Anda berdasarkan nilai, bukan dengan pointer atau dengan referensi, Anda menyalin objek (seluruh objek, apakah itu beberapa byte atau dump database besar - Anda cukup pintar untuk peduli untuk menghindari yang terakhir, bukan? t you?) setiap kali Anda melakukan '='. Dan untuk mengakses anggota objek, Anda menggunakan '.' (dot).
Ketika Anda melewati objek Anda dengan pointer, Anda hanya menyalin beberapa byte (4 pada sistem 32-bit, 8 pada yang 64-bit), yaitu - alamat objek ini. Dan untuk menunjukkan ini kepada semua orang, Anda menggunakan operator '->' mewah ini ketika Anda mengakses anggota. Atau Anda dapat menggunakan kombinasi '*' dan '.'.
Saat Anda menggunakan referensi, maka Anda mendapatkan pointer yang berpura-pura menjadi nilai. Ini adalah pointer, tetapi Anda mengakses anggota melalui '.'
Dan, untuk meniup pikiran Anda sekali lagi: ketika Anda mendeklarasikan beberapa variabel yang dipisahkan oleh koma, maka (perhatikan tangan):
Contoh:
sumber
std::auto_ptr
sudah usang, tolong jangan gunakan itu.Dalam C ++, objek yang dialokasikan pada stack (menggunakan
Object object;
pernyataan dalam blok) hanya akan hidup dalam lingkup yang dideklarasikan. Ketika blok kode selesai dieksekusi, objek yang dideklarasikan dihancurkan. Sedangkan jika Anda mengalokasikan memori pada tumpukan, menggunakanObject* obj = new Object()
, mereka terus hidup di tumpukan sampai Anda menelepondelete obj
.Saya akan membuat objek pada heap ketika saya ingin menggunakan objek tidak hanya dalam blok kode yang menyatakan / mengalokasikannya.
sumber
Object obj
tidak selalu ada di tumpukan - misalnya variabel global atau anggota.Saya akan membandingkan cara kerjanya di dalam fungsi tubuh jika Anda menggunakan:
Di dalam fungsi, Anda
myObject
akan hancur setelah fungsi ini kembali. Jadi ini berguna jika Anda tidak memerlukan objek di luar fungsi Anda. Objek ini akan diletakkan di tumpukan thread saat ini.Jika Anda menulis di dalam fungsi tubuh:
kemudian instance kelas Object yang ditunjuk oleh
myObject
tidak akan hancur setelah fungsi berakhir, dan alokasi ada di heap.Sekarang jika Anda adalah programmer Java, maka contoh kedua lebih dekat dengan bagaimana alokasi objek bekerja di bawah java. Baris ini:
Object *myObject = new Object;
setara dengan java:Object myObject = new Object();
. Perbedaannya adalah bahwa di bawah java myObject akan mengumpulkan sampah, sedangkan di bawah c ++ tidak akan dibebaskan, Anda harus secara eksplisit memanggil 'delete myObject;' jika tidak, Anda akan mengalami kebocoran memori.Sejak c ++ 11 Anda dapat menggunakan cara aman dari alokasi dinamis:,
new Object
dengan menyimpan nilai dalam shared_ptr / unique_ptr.juga, objek sangat sering disimpan dalam wadah, seperti map-s atau vektor-s, mereka akan secara otomatis mengatur seumur hidup objek Anda.
sumber
then myObject will not get destroyed once function ends
Itu benar-benar akan.myObject
masih akan dihancurkan, sama seperti variabel lokal lainnya akan. Perbedaannya adalah bahwa nilainya adalah penunjuk ke objek, bukan objek itu sendiri, dan penghancuran penunjuk bodoh tidak memengaruhi titiknya. Jadi benda itu akan selamat dari kehancuran.Secara teknis ini adalah masalah alokasi memori, namun di sini ada dua aspek yang lebih praktis dari ini. Ini ada hubungannya dengan dua hal: 1) Lingkup, ketika Anda mendefinisikan suatu objek tanpa pointer Anda tidak akan lagi dapat mengaksesnya setelah blok kode itu didefinisikan, sedangkan jika Anda mendefinisikan pointer dengan "baru" maka Anda dapat mengaksesnya dari mana saja Anda memiliki pointer ke memori ini sampai Anda memanggil "delete" pada pointer yang sama. 2) Jika Anda ingin meneruskan argumen ke suatu fungsi, Anda ingin meneruskan sebuah pointer atau referensi agar lebih efisien. Ketika Anda melewati Obyek maka objek tersebut disalin, jika ini adalah objek yang menggunakan banyak memori, ini mungkin memakan CPU (misalnya Anda menyalin vektor penuh data). Ketika Anda melewati pointer semua yang Anda lulus adalah satu int (tergantung dari implementasi tetapi kebanyakan dari mereka adalah satu int).
Selain itu, Anda perlu memahami bahwa "baru" mengalokasikan memori pada heap yang perlu dibebaskan di beberapa titik. Ketika Anda tidak harus menggunakan "baru", saya sarankan Anda menggunakan definisi objek biasa "pada tumpukan".
sumber
Nah pertanyaan utamanya adalah mengapa saya harus menggunakan pointer daripada objek itu sendiri? Dan jawaban saya, Anda seharusnya (hampir) tidak pernah menggunakan pointer daripada objek, karena C ++ memiliki referensi , lebih aman daripada pointer dan menjamin kinerja yang sama dengan pointer.
Hal lain yang Anda sebutkan dalam pertanyaan Anda:
Bagaimana cara kerjanya? Ini menciptakan pointer
Object
tipe, mengalokasikan memori agar sesuai dengan satu objek dan memanggil konstruktor default, kedengarannya bagus, kan? Tetapi sebenarnya itu tidak begitu baik, jika Anda mengalokasikan memori secara dinamis (kata kunci yang digunakannew
), Anda juga harus membebaskan memori secara manual, itu berarti dalam kode Anda harus memiliki:Ini panggilan destruktor dan membebaskan memori, terlihat mudah, namun dalam proyek-proyek besar mungkin sulit untuk mendeteksi apakah satu utas membebaskan memori atau tidak, tetapi untuk tujuan itu Anda dapat mencoba berbagi petunjuk , ini sedikit menurunkan kinerja, tetapi jauh lebih mudah untuk bekerja dengan mereka.
Dan sekarang beberapa pengantar telah selesai dan kembali ke pertanyaan.
Anda dapat menggunakan pointer alih-alih objek untuk mendapatkan kinerja yang lebih baik saat mentransfer data antar fungsi.
Lihatlah, Anda telah
std::string
(itu juga objek) dan itu berisi sangat banyak data, misalnya XML besar, sekarang Anda perlu menguraikannya, tetapi untuk itu Anda memiliki fungsivoid foo(...)
yang dapat dideklarasikan dengan cara yang berbeda:void foo(std::string xml);
Dalam hal ini Anda akan menyalin semua data dari variabel Anda ke tumpukan fungsi, itu membutuhkan waktu, sehingga kinerja Anda akan rendah.void foo(std::string* xml);
Dalam hal ini Anda akan melewatkan pointer ke objek, kecepatan yang sama sepertisize_t
variabel yang lewat , namun deklarasi ini memiliki kecenderungan kesalahan, karena Anda dapat melewatiNULL
pointer atau pointer yang tidak valid. Pointer biasanya digunakanC
karena tidak memiliki referensi.void foo(std::string& xml);
Di sini Anda melewati referensi, pada dasarnya sama dengan melewati pointer, tetapi kompiler melakukan beberapa hal dan Anda tidak dapat melewati referensi yang tidak valid (sebenarnya dimungkinkan untuk membuat situasi dengan referensi yang tidak valid, tetapi itu menipu kompiler).void foo(const std::string* xml);
Ini sama dengan yang kedua, hanya nilai pointer tidak bisa diubah.void foo(const std::string& xml);
Di sini sama dengan ketiga, tetapi nilai objek tidak dapat diubah.Apa lagi yang ingin saya sebutkan, Anda dapat menggunakan 5 cara ini untuk meneruskan data apa pun cara alokasi yang telah Anda pilih (dengan
new
atau reguler ).Satu hal lagi, ketika Anda membuat objek dengan cara biasa , Anda mengalokasikan memori dalam tumpukan, tetapi saat Anda membuatnya dengan
new
Anda mengalokasikan tumpukan. Jauh lebih cepat untuk mengalokasikan stack, tetapi ini agak kecil untuk array data yang sangat besar, jadi jika Anda membutuhkan objek besar Anda harus menggunakan heap, karena Anda mungkin mendapatkan stack overflow, tetapi biasanya masalah ini diselesaikan menggunakan wadah STL dan ingatstd::string
juga wadah, beberapa orang lupa :)sumber
Mari kita mengatakan bahwa Anda memiliki
class A
yang mengandungclass B
Bila Anda ingin memanggil beberapa fungsiclass B
di luarclass A
Anda hanya akan mendapatkan pointer ke kelas ini dan Anda dapat melakukan apapun yang Anda inginkan dan itu juga akan mengubah konteksclass B
di Andaclass A
Tapi hati-hati dengan objek yang dinamis
sumber
Ada banyak manfaat menggunakan pointer ke objek -
sumber
Ini telah dibahas panjang lebar, tetapi di Jawa semuanya adalah pointer. Itu tidak membuat perbedaan antara alokasi stack dan heap (semua objek dialokasikan pada heap), jadi Anda tidak menyadari bahwa Anda menggunakan pointer. Di C ++, Anda dapat mencampur keduanya, tergantung pada kebutuhan memori Anda. Kinerja dan penggunaan memori lebih deterministik dalam C ++ (duh).
sumber
Melakukan ini akan membuat referensi ke Obyek (di heap) yang harus dihapus secara eksplisit untuk menghindari kebocoran memori .
Melakukan ini akan membuat objek (myObject) dari tipe otomatis (pada stack) yang akan dihapus secara otomatis ketika objek (myObject) keluar dari ruang lingkup.
sumber
Pointer secara langsung merujuk lokasi memori suatu objek. Java tidak memiliki yang seperti ini. Java memiliki referensi yang merujuk lokasi objek melalui tabel hash. Anda tidak dapat melakukan hal seperti pointer aritmatika di Jawa dengan referensi ini.
Untuk menjawab pertanyaan Anda, itu hanya preferensi Anda. Saya lebih suka menggunakan sintaks mirip Java.
sumber
Dengan pointer ,
dapat langsung berbicara ke memori.
dapat mencegah banyak kebocoran memori suatu program dengan memanipulasi pointer.
sumber
Salah satu alasan untuk menggunakan pointer adalah untuk berinteraksi dengan fungsi C. Alasan lain adalah untuk menghemat memori; misalnya: alih-alih meneruskan objek yang berisi banyak data dan memiliki prosesor-konstruktor intensif untuk suatu fungsi, cukup berikan pointer ke objek tersebut, hemat memori dan kecepatan terutama jika Anda berada dalam satu lingkaran, namun referensi akan lebih baik dalam hal itu, kecuali jika Anda menggunakan array gaya-C.
sumber
Di daerah-daerah di mana pemanfaatan memori berada pada puncaknya, petunjuk berguna. Sebagai contoh, pertimbangkan algoritma minimax, di mana ribuan node akan dihasilkan menggunakan rutin rekursif, dan kemudian menggunakannya untuk mengevaluasi langkah terbaik berikutnya dalam game, kemampuan untuk melakukan deallocate atau reset (seperti pada smart pointer) secara signifikan mengurangi konsumsi memori. Sedangkan variabel non-pointer terus menempati ruang hingga panggilan rekursifnya mengembalikan nilai.
sumber
Saya akan menyertakan satu kasus penggunaan pointer yang penting. Ketika Anda menyimpan beberapa objek di kelas dasar, tetapi bisa jadi polimorfik.
Jadi dalam hal ini Anda tidak dapat mendeklarasikan bObj sebagai objek langsung, Anda harus memiliki pointer.
sumber
"Kebutuhan adalah ibu dari penemuan." Perbedaan paling penting yang ingin saya tunjukkan adalah hasil dari pengalaman saya sendiri dalam pengkodean. Terkadang Anda perlu meneruskan objek ke fungsi. Dalam hal ini, jika objek Anda adalah kelas yang sangat besar maka melewatinya sebagai objek akan menyalin keadaannya (yang mungkin tidak Anda inginkan .. DAN BISA MENJADI LEBIH BANYAK KE LUAR) sehingga menghasilkan overhead menyalin objek. Sementara pointer diperbaiki Ukuran 4-byte (dengan asumsi 32 bit). Alasan lain sudah disebutkan di atas ...
sumber
std::string test;
kita milikivoid func(const std::string &) {}
tetapi kecuali fungsi perlu mengubah input dalam hal ini saya sarankan menggunakan pointer (sehingga siapa pun yang membaca kode tidak melihat&
, dan memahami fungsi dapat mengubah inputnya)Sudah ada banyak jawaban bagus, tetapi izinkan saya memberi Anda satu contoh:
Saya memiliki kelas Item sederhana:
Saya membuat vektor untuk menampung banyak dari mereka.
std::vector<Item> inventory;
Saya membuat satu juta objek Item, dan mendorongnya kembali ke vektor. Saya mengurutkan vektor berdasarkan nama, dan kemudian melakukan pencarian biner iteratif sederhana untuk nama item tertentu. Saya menguji program, dan dibutuhkan lebih dari 8 menit untuk menyelesaikan eksekusi. Lalu saya mengubah vektor inventaris saya seperti:
std::vector<Item *> inventory;
... dan buat sejuta objek Item saya melalui yang baru. HANYA perubahan yang saya buat pada kode saya adalah dengan menggunakan pointer ke Items, kecuali sebuah loop yang saya tambahkan untuk pembersihan memori pada akhirnya. Program itu berjalan di bawah 40 detik, atau lebih baik dari peningkatan kecepatan 10x. EDIT: Kode ini di http://pastebin.com/DK24SPeW Dengan optimisasi kompiler hanya menunjukkan peningkatan 3,4x pada mesin saya baru saja mengujinya, yang masih cukup besar.
sumber
push_back
. Tentu saja salinan ini. Anda seharusnya beradaemplace
di tempat ketika membuat objek Anda (kecuali Anda membutuhkannya untuk di-cache di tempat lain).