Sebagian besar jawaban di sini gagal mengatasi ambiguitas yang melekat dalam memiliki pointer mentah dalam tanda tangan fungsi, dalam hal mengekspresikan niat. Masalahnya adalah sebagai berikut:
Penelepon tidak tahu apakah penunjuk menunjuk ke objek tunggal, atau ke awal "array" objek.
Penelepon tidak tahu apakah pointer "memiliki" memori yang ditunjuknya. Yaitu apakah fungsi tersebut harus membebaskan memori atau tidak. ( foo(new int)
- Apakah ini kebocoran memori?).
Penelepon tidak tahu apakah nullptr
bisa dengan aman masuk ke dalam fungsi.
Semua masalah ini diselesaikan dengan referensi:
Referensi selalu merujuk ke satu objek.
Referensi tidak pernah memiliki ingatan yang mereka rujuk, mereka hanyalah pandangan ke ingatan.
Referensi tidak boleh nol.
Ini menjadikan referensi kandidat yang jauh lebih baik untuk penggunaan umum. Namun, referensi tidak sempurna - ada beberapa masalah besar yang perlu dipertimbangkan.
- Tidak ada tipuan eksplisit. Ini bukan masalah dengan pointer mentah, karena kita harus menggunakan
&
operator untuk menunjukkan bahwa kita memang melewati pointer. Sebagai contoh, int a = 5; foo(a);
Tidak jelas sama sekali di sini bahwa a sedang disahkan oleh referensi dan dapat dimodifikasi.
- Nullability. Kelemahan pointer ini juga bisa menjadi kekuatan, ketika kita benar - benar ingin referensi kita menjadi nol. Melihat sebagai
std::optional<T&>
tidak valid (untuk alasan yang baik), petunjuk memberi kami nullability yang Anda inginkan.
Jadi sepertinya ketika kita menginginkan referensi yang dapat dibatalkan dengan tipuan eksplisit, kita harus meraih T*
hak? Salah!
Abstraksi
Dalam keputus-asaan kita untuk nullability, kita dapat meraih T*
, dan mengabaikan semua kekurangan dan ambiguitas semantik yang disebutkan sebelumnya. Sebagai gantinya, kita harus meraih apa yang paling baik dilakukan C ++: abstraksi. Jika kita hanya menulis kelas yang membungkus sebuah pointer, kita memperoleh ekspresif, serta nullability dan tipuan eksplisit.
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
Ini adalah antarmuka paling sederhana yang bisa saya buat, tapi itu berfungsi dengan efektif. Ini memungkinkan untuk menginisialisasi referensi, memeriksa apakah suatu nilai ada dan mengakses nilai tersebut. Kita bisa menggunakannya seperti ini:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
Kami telah mencapai tujuan kami! Sekarang mari kita lihat manfaatnya, dibandingkan dengan pointer mentah.
- Antarmuka menunjukkan dengan jelas bahwa referensi hanya merujuk pada satu objek.
- Jelas itu tidak memiliki memori yang dimaksud, karena tidak memiliki destruktor yang ditentukan pengguna dan tidak ada metode untuk menghapus memori.
- Pemanggil tahu
nullptr
dapat diteruskan, karena fungsi penulis secara eksplisit memintaoptional_ref
Kita bisa membuat antarmuka lebih kompleks dari sini, seperti menambahkan operator kesetaraan, monadik get_or
dan map
antarmuka, metode yang mendapatkan nilai atau melempar pengecualian, constexpr
dukungan. Itu bisa dilakukan oleh Anda.
Sebagai kesimpulan, alih-alih menggunakan pointer mentah, alasan tentang apa yang sebenarnya dimaksud pointer dalam kode Anda, dan baik memanfaatkan abstraksi perpustakaan standar atau menulis milik Anda. Ini akan meningkatkan kode Anda secara signifikan.
new
untuk membuat pointer dan masalah kepemilikan yang dihasilkan.