Apakah semua objek dalam C ++ bisa berubah jika tidak dinyatakan sebaliknya?

8

Apakah setiap objek dalam C ++ bisa berubah jika tidak dinyatakan sebaliknya?

Dalam Python dan Javascript saya tidak bisa mengubah string, tuple, unicodes. Saya bertanya-tanya apakah ada sesuatu di C ++ yang tidak berubah atau setiap objek bisa berubah dan saya harus menggunakan constjenis kualifikasi untuk membuatnya tidak berubah.

GM
sumber
2
apa maksudmu dengan "secara default"?
BЈовић
Bahwa semua objek di C ++ bisa berubah jika tidak dinyatakan berbeda.
GM
1
Iya itu mereka. Perhatikan ini tidak sama dengan kata kunci "bisa berubah".
Olav
3
Pertanyaan dalam badan pertanyaan adalah pertanyaan yang berbeda dari pertanyaan dalam judul?
user253751
@immibis itu hanya untuk menambah sedikit kontes
GM

Jawaban:

18

Kekekalan telah dipahami dengan baik untuk beberapa waktu. Python, Java, dan C ++ memiliki model memori berbeda yang membuat perbandingan langsung menjadi sulit. Penulis artikel yang Anda kutip tampaknya tidak tahu C ++.

Seperti dalam Python, Java, dan sebagian besar bahasa multi-paradigma, C dan C ++ memungkinkan mutabilitas secara default. Inilah yang biasanya diinginkan oleh programmer: jika saya memiliki String xvariabel, saya ingin dapat memberikan nilai baru x = "foo".

Const-system di C dan C ++ memungkinkan banyak ketidakberdayaan bernuansa yang kurang dalam Python, Java, dan bahkan Scala. Jika fungsi C ++ mengambil a const std::string&atau a const char*, ia menjanjikan (dan kompiler memastikan, sampai tingkat tertentu), bahwa fungsi ini tidak akan (tidak bisa!) Mengubah isi string itu. Diberikan objek const, kita hanya bisa memanggil metode objek yang juga ditandai sebagai const. Jika kelas C ++ hanya memiliki anggota publik yang const, maka objek secara efektif tidak dapat diubah.

Namun, ini kadang membingungkan karena dalam objek C dan C ++ lokasi memori, dan variabel adalah nama untuk lokasi memori. Sebaliknya, variabel dalam Python dan Java adalah nama untuk pointer ke objek. Dalam bahasa dengan semantik referensi, x = yberarti "buat x arahkan ke objek yang sama dengan y". Karena kita hanya menyalin pointer, ini dimungkinkan dengan objek yang tidak berubah. Dalam bahasa dengan semantik nilai seperti C ++, itu berarti "perbarui konten xdengan konten y". Oleh karena itu, jika penugasan kembali suatu variabel diinginkan dalam C atau C ++, variabel tersebut mungkin tidak memiliki tipe const. Untuk melakukan ini dengan objek yang tidak dapat diubah, kita harus menggunakan pointer secara eksplisit.

Bahwa Java dan Python menggunakan objek string yang tidak dapat diubah adalah keputusan desain dasar, tetapi tidak secara langsung terhubung ke manfaat ketetapan dalam lingkungan multithreading. Salah satu alasannya adalah bahwa string literal dalam kode sumber dapat dikumpulkan yang mengurangi jumlah objek. Ini dimungkinkan dalam C / C ++ juga. Dalam C ++ literal "foo"memiliki tipe const char[4](karakter ke-4 adalah terminating '\0'). Alasan lain adalah bahwa entri dalam set dan kunci dalam dicts / peta tidak boleh diubah. Karena string digunakan secara luas sebagai kunci dict (sebagian besar objek Python adalah dict), immutability menghapus sumber kesalahan yang umum. Di Jawa, alasan lain untuk string yang tidak dapat diubah adalah model keamanan Java. Semua alasan ini sama sekali tidak terkait dengan multithreading.

Jika Jawa dibangun dengan pertimbangan kekekalan, bahasa akan terlihat sangat berbeda. Meskipun sangat terinspirasi oleh C ++, para desainer berusaha keras untuk membuat bahasa yang lebih sederhana, menghilangkan const adalah salah satu langkahnya. Hal Java yang setara dengan referensi const C ++ adalah adaptor atau dekorator yang mengimplementasikan metode mutasi apa pun throws new NotImplementedException(), dan meneruskan metode non-mutasi panggilan ke koleksi aktual. Fakta bahwa antarmuka java.util interface semua menyiratkan mutabilitas adalah tanda yang jelas bahwa mereka tidak berusaha untuk bahasa pertama-immutability.

Solusi yang diajukan Java untuk memecahkan masalah konkurensi bukanlah kekekalan, tetapi penguncian yang luas. Setiap objek tunggal berisi mutex yang dapat digunakan untuk synchronizedblok atau seluruh metode. Ternyata, itu tidak baik untuk kinerja, tidak skala dengan sangat baik, dan cukup rawan kesalahan - Anda masih harus berurusan dengan negara global yang bisa berubah.

amon
sumber
13
Inilah yang biasanya diinginkan oleh programmer => itu klaim yang cukup kontroversial. Pengalaman saya sebenarnya sebaliknya: sebagian besar variabel lokal tidak berubah, dengan yang kadang-kadang bisa berubah. Bahasa-bahasa seperti Rust (di mana mutabilitas membutuhkan mutkata kunci, dan di mana mutabilitas yang tidak perlu ditandai oleh kompiler) cenderung mendukung hipotesis saya: di Rust, Anda memiliki lebih banyak letbinding daripada yang Anda miliki let mut.
Matthieu M.
1
@ MatthieuM .: Itu sebagian karena for x in 1..10tidak digunakan let mutuntuk mendefinisikan variabel loop, tapi jelas itu membutuhkan beberapa mutabilitas (hanya pada level loop, tidak di dalam tubuh loop).
MSalters
@ MSalters: Tentu saja, tetapi C ++ juga memiliki rentang-untuk sintaks :)
Matthieu M.
4
@ MSalters Saya akan mencirikan bahwa sebagai mengikat 10 variabel yang disebut x dengan nilai 10, tidak bermutasi satu
Caleth
1
@ MatthieuM. Saya setuju bahwa imutabilitas itu baik, tetapi dalam C ++ itu tidak selalu merupakan pilihan, atau setidaknya terlalu canggung. Misalnya menginisialisasi variabel const dalam beberapa langkah mengharuskan kita untuk melakukan itu dalam fungsi terpisah di mana objek tersebut belum const. Setidaknya C ++ 11 mari kita gunakan lambda yang dipanggil langsung seperti const T o = [&]{T o; o.init(...); return o;}();... yay untuk API yang tidak dirancang dengan pertimbangan keabadian.
amon
9

Hampir. Bahasa itu sendiri memperlakukan segala sesuatu sebagai bisa berubah kecuali Anda menggunakan const, tetapi masih mungkin untuk membangun objek tidak berubah tanpa memberitahu kompiler mereka tidak dapat diubah, dengan hanya tidak menyediakan cara untuk bermutasi.

Sebagai contoh, ambil kelas ini:

class MyClass {
    int value;
public:

    MyClass(int v) : value(v) {}
    int getValue() {return value;}

    MyClass &operator=(const MyClass &other) = delete;
};

Contoh dari MyClasstidak dapat diubah, karena tidak ada cara untuk memutasi mereka - tetapi tidak ada dalam kode yang benar-benar menyatakan mereka tidak dapat diubah. (Ini adalah cara yang sama bahwa instance Stringkelas Java tidak berubah)

pengguna253751
sumber
(Saya mulai berkarat) Apakah deleteoperator penugasan salinan di sini juga memastikan tidak dapat menetapkan dari referensi nilai melalui operasi pemindahan-tugas?
underscore_d
Dan apakah menghancurkan objek dengan panggilan destruktor eksplisit dan kemudian menciptakannya kembali dengan menempatkan hitungan baru sebagai "bermutasi"?
Peter Green
1
@underscore_d Ini dianggap sebagai operator yang mendefinisikan pengguna untuk tujuan tidak secara implisit menambahkan tugas pindah, ya
Caleth
Saya bertanya-tanya apakah mekanisme untuk membiarkan kompiler tahu bahwa objek dari kelas tertentu selalu berubah dapat mengarah pada optimasi yang lebih baik. Misalnya tidak perlu menyegarkan objek yang diuangkan. Dalam contoh khusus ini (dan umumnya dalam kelas mengikuti pola ini) mungkin akan membantu untuk mendeklarasikan valueconst, dan untuk mendeklarasikan getValue()fungsi const.
Peter - Reinstate Monica
1
@PeterGreen Tidak, yang dianggap sebagai menghancurkan objek dan membuat yang baru.
user253751
2

Tidak, ada satu pengecualian: Lambdas dan ekstensi objek yang mereka tangkap tidak dapat diubah secara default:

int i = 0;

[i]{//capturing the value i
    i++; //does not compile, i is const
};

[i]() mutable{ //make the lambda mutable
    i++; //compiles fine
};

Jadi, alih-alih menambahkan constuntuk membuatnya tidak berubah, Anda menambahkan mutableuntuk membuatnya tidak const.

nwp
sumber