Jadi pada titik apa kelas menjadi terlalu rumit untuk tidak dapat diubah?
Menurut pendapat saya, tidak ada gunanya membuat kelas kecil tidak dapat diubah dalam bahasa seperti yang Anda tampilkan. Saya menggunakan kecil di sini dan tidak kompleks , karena bahkan jika Anda menambahkan sepuluh bidang ke kelas itu dan itu benar-benar suka operasi pada mereka, saya ragu itu akan mengambil kilobyte apalagi megabyte apalagi gigabyte, jadi fungsi apa pun menggunakan instance dari Anda kelas dapat dengan mudah membuat salinan murah dari seluruh objek untuk menghindari memodifikasi yang asli jika ingin menghindari menyebabkan efek samping eksternal.
Struktur Data Persisten
Di mana saya menemukan penggunaan pribadi untuk kekekalan adalah untuk struktur data pusat yang besar yang mengagregasi sekelompok kecil data seperti contoh dari kelas yang Anda tampilkan, seperti yang menyimpan sejuta NamedThings
. Dengan menjadi bagian dari struktur data persisten yang tidak dapat diubah dan berada di belakang antarmuka yang hanya memungkinkan akses baca-saja, elemen-elemen yang menjadi milik kontainer menjadi tidak dapat diubah tanpa kelas elemen ( NamedThing
) yang harus menghadapinya.
Salinan Murah
Struktur data yang persisten memungkinkan bagian-bagiannya ditransformasi dan dibuat unik, menghindari modifikasi ke aslinya tanpa harus menyalin struktur data secara keseluruhan. Itulah keindahan yang sebenarnya. Jika Anda ingin secara naif menulis fungsi yang menghindari efek samping yang menginput struktur data yang membutuhkan gigabytes memori dan hanya memodifikasi memori senilai satu megabyte, maka Anda harus menyalin seluruh hal aneh untuk menghindari menyentuh input dan mengembalikan yang baru keluaran. Baik menyalin gigabyte untuk menghindari efek samping atau menyebabkan efek samping dalam skenario itu, membuat Anda harus memilih di antara dua pilihan yang tidak menyenangkan.
Dengan struktur data yang persisten, ini memungkinkan Anda untuk menulis fungsi seperti itu dan menghindari membuat salinan dari seluruh struktur data, hanya membutuhkan sekitar satu megabita memori ekstra untuk output jika fungsi Anda hanya mengubah nilai memori satu megabita.
Beban
Adapun beban, ada yang langsung setidaknya dalam kasus saya. Saya perlu pembangun yang dibicarakan orang atau "transien" saat saya memanggil mereka untuk dapat secara efektif mengekspresikan transformasi pada struktur data besar itu tanpa menyentuhnya. Kode seperti ini:
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
... maka harus ditulis seperti ini:
ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
// Grab a "transient" (builder) list we can modify:
TransientList<Stuff> transient(stuff);
// Transform stuff in the range, [first, last)
// for the transient list.
for (; first != last; ++first)
transform(transient[first]);
// Commit the modifications to get and return a new
// immutable list.
return stuff.commit(transient);
}
Tetapi sebagai ganti dari dua baris kode tambahan, fungsi sekarang aman untuk memanggil utas dengan daftar asli yang sama, itu menyebabkan tidak ada efek samping, dll. Ini juga membuatnya sangat mudah untuk membuat operasi ini menjadi tindakan pengguna yang tidak dapat diurungkan karena batalkan hanya dapat menyimpan salinan murah dari daftar lama.
Pengecualian-Keamanan atau Pemulihan Kesalahan
Tidak semua orang dapat mengambil manfaat sebanyak yang saya lakukan dari struktur data yang persisten dalam konteks seperti ini (saya menemukan banyak kegunaannya dalam sistem undo dan pengeditan non-destruktif yang merupakan konsep sentral dalam domain VFX saya), tetapi satu hal yang dapat diterapkan pada hampir semua semua orang untuk dipertimbangkan adalah pengecualian-keselamatan atau pemulihan kesalahan .
Jika Anda ingin membuat fungsi mutasi yang asli pengecualian-aman, maka itu memerlukan logika rollback, yang untuk itu implementasi paling sederhana memerlukan menyalin seluruh daftar:
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Make a copy of the whole massive gigabyte-sized list
// in case we encounter an exception and need to rollback
// changes.
MutList<Stuff> old_stuff = stuff;
try
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
catch (...)
{
// If the operation failed and ran into an exception,
// swap the original list with the one we modified
// to "undo" our changes.
stuff.swap(old_stuff);
throw;
}
}
Pada titik ini versi pengecualian-aman yang dapat diubah bahkan lebih mahal secara komputasi dan bahkan lebih sulit untuk menulis dengan benar daripada versi yang tidak dapat diubah menggunakan "builder". Dan banyak pengembang C ++ mengabaikan keamanan-pengecualian dan mungkin itu baik-baik saja untuk domain mereka, tetapi dalam kasus saya saya ingin memastikan kode saya berfungsi dengan benar bahkan jika ada pengecualian (bahkan menulis tes yang dengan sengaja melemparkan pengecualian untuk menguji pengecualian) keamanan), dan itu membuatnya jadi saya harus bisa mengembalikan semua efek samping yang menyebabkan fungsi setengah jalan jika ada sesuatu yang dilemparkan.
Ketika Anda ingin menjadi pengecualian-aman dan pulih dari kesalahan dengan anggun tanpa aplikasi Anda mogok dan terbakar, maka Anda harus mengembalikan / membatalkan segala efek samping yang dapat disebabkan oleh suatu fungsi jika terjadi kesalahan / pengecualian. Dan di sana pembangun sebenarnya dapat menghemat lebih banyak waktu programmer daripada biaya seiring dengan waktu komputasi karena: ...
Anda tidak perlu khawatir tentang mengembalikan efek samping dalam suatu fungsi yang tidak menyebabkan apa pun!
Jadi kembali ke pertanyaan mendasar:
Pada titik apakah kelas yang tidak dapat berubah menjadi beban?
Mereka selalu menjadi beban dalam bahasa yang lebih banyak berkisar seputar mutabilitas daripada ketidakmampuan, itulah sebabnya saya pikir Anda harus menggunakannya di mana manfaatnya jauh lebih besar daripada biayanya. Tetapi pada tingkat yang cukup luas untuk struktur data yang cukup besar, saya percaya ada banyak kasus di mana itu merupakan trade-off yang layak.
Juga di tambang, saya hanya memiliki beberapa tipe data yang tidak dapat diubah, dan semuanya merupakan struktur data besar yang dimaksudkan untuk menyimpan sejumlah besar elemen (piksel gambar / tekstur, entitas dan komponen ECS, dan simpul / tepi / poligon dari sebuah jaring).