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
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?
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:
{
std::string s;
}
// s is destroyed here
Di sisi lain, jika Anda menggunakan pointer yang dialokasikan secara dinamis, penghancurnya harus dipanggil secara manual. delete
memanggil destruktor ini untuk Anda.
{
std::string* s = new std::string;
}
delete s; // destructor called
Ini tidak ada hubungannya dengan new
sintaks yang lazim di C # dan Java. Mereka digunakan untuk tujuan yang sama sekali berbeda.
Manfaat alokasi dinamis
1. Anda tidak perlu tahu ukuran array terlebih dahulu
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:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Tentu saja, jika Anda menggunakan std::string
gantinya, secara std::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:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Catatan : Satu kesalahan yang banyak dilakukan oleh pemula adalah penggunaan array panjang variabel. Ini adalah ekstensi GNU dan juga satu di Dentang karena mereka mencerminkan banyak ekstensi GCC. Jadi yang berikut ini
int arr[n]
jangan dijadikan andalan.
Karena tumpukan jauh lebih besar dari tumpukan, seseorang dapat secara sewenang-wenang mengalokasikan / mengalokasikan kembali memori sebanyak yang dia butuhkan, sedangkan tumpukan memiliki batasan.
2. Array bukan pointer
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:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
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.
3. Polimorfisme
Java dan C # memiliki fasilitas yang memungkinkan Anda untuk memperlakukan objek sebagai objek lain, misalnya menggunakan as
kata kunci. Jadi, jika seseorang ingin memperlakukan Entity
objek sebagai Player
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:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
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 mensimulasikan as
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.
1. (N) RVO - Aka, (Bernama) Optimasi Nilai Kembali
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.