Apakah kata kunci yang 'dapat diubah-ubah' memiliki tujuan selain membiarkan variabelnya dimodifikasi oleh fungsi const?


527

Beberapa waktu yang lalu saya menemukan beberapa kode yang menandai variabel anggota kelas dengan mutablekata kunci. Sejauh yang saya lihat, ini memungkinkan Anda untuk memodifikasi variabel dalam suatu constmetode:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

Apakah ini satu-satunya penggunaan kata kunci ini atau ada lebih dari itu memenuhi mata? Saya telah menggunakan teknik ini di kelas, menandai fungsi yang boost::mutexbisa berubah yang memungkinkan constuntuk menguncinya karena alasan keamanan, tetapi, jujur ​​saja, rasanya seperti sedikit peretasan.


2
Sebuah pertanyaan, jika Anda tidak mengubah apa pun, mengapa Anda harus menggunakan mutex? Saya hanya ingin memahami ini.
Misgevolution

@Misgevolution Anda memodifikasi sesuatu, Anda hanya mengendalikan siapa / bagaimana dapat melakukan modifikasi melalui const. Contoh yang sangat naif, bayangkan jika saya hanya memberikan pegangan non-const kepada teman, musuh mendapatkan handle const. Teman dapat memodifikasi, musuh tidak bisa.
iheanyi

1
Catatan: inilah contoh yang bagus untuk menggunakan kata kunci mutable: stackoverflow.com/questions/15999123/…
Gabriel Staples

Saya berharap ini dapat digunakan untuk menimpa const(tipe) jadi saya tidak perlu melakukan ini class A_mutable{}; using A = A_mutable const; mutable_t<A> a;:, jika saya ingin konst-by-default, yaitu mutable A a;(dapat diubah secara eksplisit) dan A a;(konstit implisit).
alfC

Jawaban:


351

Ini memungkinkan diferensiasi konstanta bitwise dan konstanta logis. Konstanta logis adalah ketika suatu objek tidak berubah dengan cara yang terlihat melalui antarmuka publik, seperti contoh penguncian Anda. Contoh lain adalah kelas yang menghitung nilai saat pertama kali diminta, dan menyimpan hasilnya.

Karena c ++ 11 mutabledapat digunakan pada lambda untuk menyatakan bahwa hal-hal yang ditangkap oleh nilai dapat dimodifikasi (tidak secara default):

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda

52
'bisa berubah' sama sekali tidak mempengaruhi bitwise / logical constness. C ++ hanya konstanta bitwise dan kata kunci 'bisa berubah' dapat digunakan untuk mengecualikan anggota dari pemeriksaan ini. Tidak mungkin untuk mencapai const 'logis' di C ++ selain melalui abstraksi (mis. SmartPtrs).
Richard Corden

111
@ Richard: Anda tidak mengerti intinya. Tidak ada kata kunci "konstanta logis", benar, melainkan, itu adalah diferensiasi konseptual yang dibuat programmer untuk memutuskan anggota mana yang harus dikeluarkan dengan dibuat berubah-ubah, berdasarkan pada pemahaman tentang apa yang merupakan keadaan logis objek yang dapat diamati secara logis.
Tony Delroy

6
@ Jay Ya, itu adalah seluruh titik markig variabel anggota sebagai bisa berubah, untuk memungkinkannya diubah dalam objek const.
KeithB

6
Mengapa seseorang harus bisa berubah pada lambdas? Bukankah cukup untuk menangkap variabel dengan referensi?
Giorgio

11
@Iorgio: Perbedaannya adalah bahwa modifikasi xdi dalam lambda tetap di dalam lambda, yaitu fungsi lambda hanya dapat memodifikasi salinannya sendiri x. Perubahan tidak terlihat di luar, aslinya xmasih tidak berubah. Pertimbangkan bahwa lambdas diimplementasikan sebagai kelas functor; variabel yang ditangkap sesuai dengan variabel anggota.
Sebastian Mach

138

Kata mutablekunci adalah cara untuk menembus constselubung yang Anda gantungkan pada objek Anda. Jika Anda memiliki referensi const atau pointer ke suatu objek, Anda tidak dapat memodifikasi objek itu dengan cara apa pun kecuali kapan dan bagaimana itu ditandai mutable.

Dengan constreferensi atau penunjuk, Anda dibatasi untuk:

  • hanya akses baca untuk setiap anggota data yang terlihat
  • izin untuk memanggil hanya metode yang ditandai sebagai const.

The mutablepengecualian membuatnya begitu Anda sekarang dapat menulis atau anggota kumpulan data yang ditandai mutable. Itulah satu-satunya perbedaan yang terlihat secara eksternal.

Secara internal, constmetode yang terlihat oleh Anda juga dapat menulis ke anggota data yang ditandai mutable. Pada dasarnya cadar itu ditusuk secara komprehensif. Terserah sepenuhnya kepada perancang API untuk memastikan bahwa mutabletidak merusak constkonsep dan hanya digunakan dalam kasus khusus yang bermanfaat. Kata mutablekunci membantu karena dengan jelas menandai anggota data yang tunduk pada kasus khusus ini.

Dalam praktiknya Anda dapat menggunakan secara constobsesif di seluruh basis kode Anda (Anda pada dasarnya ingin "menginfeksi" basis kode Anda dengan const"penyakit"). Di dunia ini, petunjuk dan rujukan adalahconst dengan sangat sedikit pengecualian, menghasilkan kode yang lebih mudah untuk dipikirkan dan dipahami. Untuk penyimpangan yang menarik lihat "transparansi referensial".

Tanpa mutablekata kunci, Anda pada akhirnya akan dipaksa untuk menggunakan const_castberbagai kasus khusus yang berguna yang dibolehkannya (caching, penghitungan referensi, data debug, dll.). Sayangnya const_castsecara signifikan lebih merusak daripada mutablekarena memaksa klien API untuk menghancurkan constperlindungan objek yang dia gunakan. Selain itu menyebabkan constkerusakan luas : const_casting pointer atau referensi const memungkinkan menulis tanpa terkecuali dan metode panggilan akses ke anggota yang terlihat. Sebaliknya mutablemengharuskan perancang API untuk melakukan kontrol halus atas constpengecualian, dan biasanya pengecualian ini tersembunyi dalam constmetode yang beroperasi pada data pribadi.

(NB Saya merujuk pada visibilitas data dan metode beberapa kali. Saya berbicara tentang anggota yang ditandai sebagai publik vs privat atau dilindungi yang merupakan jenis perlindungan objek yang sama sekali berbeda yang dibahas di sini .)


8
Plus, menggunakan const_castuntuk mengubah bagian dari constobjek menghasilkan perilaku yang tidak terdefinisi.
Brian

Saya tidak setuju dengan itu karena memaksa klien API untuk menghancurkan perlindungan const dari objek . Jika Anda menggunakan const_castuntuk menerapkan mutasi variabel anggota dalam suatu constmetode, Anda tidak akan meminta klien untuk melakukan cast - Anda akan melakukannya dalam metode dengan const_casting this. Pada dasarnya ini memungkinkan Anda memintas konstanta pada anggota sewenang-wenang di situs panggilan tertentu , sementara mutablemari Anda menghapus const pada anggota tertentu di semua situs panggilan. Yang terakhir biasanya adalah apa yang Anda inginkan untuk penggunaan khusus (caching, statistik), tetapi kadang-kadang const_cast sesuai dengan pola.
BeeOnRope

1
The const_castpola tidak cocok lebih baik dalam beberapa kasus, seperti ketika Anda ingin sementara memodifikasi anggota, kemudian mengembalikannya (cukup banyak seperti boost::mutex). Metode ini secara logis const karena kondisi akhir sama dengan yang awal, tetapi Anda ingin membuat perubahan sementara. const_castdapat berguna di sana karena memungkinkan Anda membuang const secara khusus dalam metode yang Anda mutasi akan dibatalkan, tetapi mutabletidak akan sesuai karena akan menghapus perlindungan const dari semua metode, yang tidak harus semua mengikuti "do , batalkan "pola.
BeeOnRope

2
Kemungkinan penempatan objek yang didefinisikan oleh const ke dalam memori read-only (lebih umum, memory ditandai read-only) dan bahasa standar terkait yang memungkinkan ini membuat const_castbom waktu menjadi mungkin. mutabletidak memiliki masalah seperti itu karena objek tersebut tidak dapat ditempatkan dalam memori hanya baca.
BeeOnRope

75

Penggunaan Anda dengan boost :: mutex tepat untuk tujuan kata kunci ini. Penggunaan lain adalah untuk cache hasil internal untuk mempercepat akses.

Pada dasarnya, 'bisa berubah' berlaku untuk setiap atribut kelas yang tidak mempengaruhi keadaan objek yang terlihat secara eksternal.

Dalam kode sampel dalam pertanyaan Anda, bisa berubah-ubah mungkin tidak sesuai jika nilai done_ mempengaruhi status eksternal, itu tergantung pada apa yang ada di ...; bagian.


35

Mutable adalah untuk menandai atribut tertentu yang dapat dimodifikasi dari dalam constmetode. Itulah satu-satunya tujuan. Pikirkan baik-baik sebelum menggunakannya, karena kode Anda mungkin akan lebih bersih dan lebih mudah dibaca jika Anda mengubah desain daripada menggunakannya mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

Jadi jika kegilaan di atas bukan untuk apa bisa berubah, untuk apa ini? Inilah kasus halus: bisa berubah adalah untuk kasus di mana objek secara logis konstan, tetapi dalam praktiknya perlu berubah. Kasus-kasus ini sedikit dan jarang, tetapi ada.

Contoh yang penulis berikan meliputi variabel cache dan debugging sementara.


2
Saya pikir tautan ini memberikan contoh terbaik dari skenario di mana mutable sangat membantu. Hampir tampaknya mereka secara eksklusif digunakan untuk debugging. (per penggunaan yang benar)
antusias_static

Penggunaan mutabledapat membuat kode lebih mudah dibaca dan bersih. Dalam contoh berikut ini, readbisa constseperti yang diharapkan. `mutable m_mutex; Kontainer m_container; membatalkan penambahan (Item item) {Lockguard lock (m_mutex); m_container.pushback (item); } Item read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Th. Thielemann

Ada satu kasus penggunaan yang sangat populer: jumlah referensi.
Seva Alekseyev

33

Ini berguna dalam situasi di mana Anda memiliki kondisi internal tersembunyi seperti cache. Sebagai contoh:

kelas HashTable
{
...
publik:
    pencarian string (kunci string) const
    {
        if (key == lastKey)
            mengembalikan LastValue;

        nilai string = lookupInternal (kunci);

        lastKey = kunci;
        lastValue = nilai;

        nilai pengembalian;
    }

pribadi:
    string dapat diubah lastKey, lastValue;
};

Dan kemudian Anda dapat memiliki const HashTableobjek yang masih menggunakan lookup()metodenya, yang memodifikasi cache internal.


9

mutable memang ada saat Anda menyimpulkan untuk memungkinkan seseorang untuk mengubah data dalam fungsi yang konstan.

Maksudnya adalah bahwa Anda mungkin memiliki fungsi yang "tidak melakukan apa-apa" pada keadaan internal objek, dan jadi Anda menandai fungsinya const, tetapi Anda mungkin benar-benar perlu memodifikasi beberapa objek dengan cara yang tidak memengaruhi yang benar. Kegunaan.

Kata kunci dapat bertindak sebagai petunjuk untuk kompiler - kompilator teoretis dapat menempatkan objek konstan (seperti global) dalam memori yang ditandai hanya baca. Adanya mutablepetunjuk bahwa ini tidak boleh dilakukan.

Berikut adalah beberapa alasan yang sah untuk menyatakan dan menggunakan data yang bisa diubah:

  • Keamanan benang. Mendeklarasikan a mutable boost::mutexsangat masuk akal.
  • Statistik. Menghitung jumlah panggilan ke suatu fungsi, mengingat sebagian atau seluruh argumennya.
  • Memoisasi. Menghitung beberapa jawaban mahal, dan kemudian menyimpannya untuk referensi di masa mendatang daripada menghitung ulang lagi.

2
Jawaban yang bagus, kecuali komentar tentang bisa berubah menjadi "petunjuk". Ini membuatnya tampak seolah-olah anggota yang bisa berubah kadang-kadang tidak akan bisa berubah jika kompilator menempatkan objek ke dalam ROM. Perilaku bisa berubah didefinisikan dengan baik.
Richard Corden

2
Selain menempatkan objek const dalam memori read-only, kompiler juga dapat memutuskan untuk mengoptimalkan panggilan konstitusi keluar dari loop, misalnya. Penghitung statistik yang dapat berubah dalam fungsi konstanta yang lain masih akan memungkinkan pengoptimalan tersebut (dan hanya menghitung satu panggilan) alih-alih mencegah pengoptimalan hanya demi menghitung lebih banyak panggilan.
Hagen von Eitzen

@HagenvonEitzen - Saya cukup yakin itu tidak benar. Kompiler tidak dapat mengangkat fungsi dari loop kecuali jika dapat membuktikan tidak ada efek samping. Bukti itu umumnya melibatkan sebenarnya memeriksa pelaksanaan fungsi (sering setelah digariskan) dan tidak bergantung pada const(dan inspeksi semacam itu akan berhasil atau gagal terlepas dari constatau mutable). Cukup mendeklarasikan fungsi consttidak cukup: suatu constfungsi bebas untuk memiliki efek samping seperti memodifikasi variabel global atau sesuatu yang diteruskan ke fungsi, jadi itu bukan jaminan yang berguna untuk bukti itu.
BeeOnRope

Sekarang, beberapa kompiler memiliki ekstensi khusus seperti _ccribute _ _ gcc ((const)) dan _ _ kontribusi _ ((pure)), yang _do memiliki efek seperti itu , tetapi itu hanya terkait secara tangensial dengan constkata kunci dalam C ++.
BeeOnRope

8

Ya, itulah yang dilakukannya. Saya menggunakannya untuk anggota yang dimodifikasi oleh metode yang tidak secara logis mengubah keadaan kelas - misalnya, untuk mempercepat pencarian dengan menerapkan cache:

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

Sekarang, Anda harus menggunakan ini dengan hati-hati - masalah konkurensi menjadi perhatian besar, karena penelepon mungkin berasumsi bahwa mereka aman jika hanya menggunakan constmetode. Dan tentu saja, memodifikasi mutabledata tidak boleh mengubah perilaku objek dengan cara yang signifikan, sesuatu yang dapat dilanggar dengan contoh yang saya berikan jika, misalnya, diharapkan bahwa perubahan yang ditulis ke disk akan segera terlihat oleh aplikasi. .


6

Mutable digunakan ketika Anda memiliki variabel di dalam kelas yang hanya digunakan di dalam kelas itu untuk memberi sinyal hal-hal seperti misalnya mutex atau kunci. Variabel ini tidak mengubah perilaku kelas, tetapi diperlukan untuk menerapkan keamanan thread kelas itu sendiri. Jadi jika tanpa "bisa berubah", Anda tidak akan dapat memiliki fungsi "const" karena variabel ini perlu diubah di semua fungsi yang tersedia untuk dunia luar. Oleh karena itu, dapat diubah diperkenalkan untuk membuat variabel anggota dapat ditulis bahkan oleh fungsi const.

Yang ditentukan bisa berubah menginformasikan baik kompiler dan pembaca bahwa itu aman dan diharapkan bahwa variabel anggota dapat dimodifikasi dalam fungsi anggota konst.


4

bisa berubah terutama digunakan pada detail implementasi kelas. Pengguna kelas tidak perlu tahu tentang hal itu, oleh karena itu metode yang menurutnya "harus" menjadi const. Contoh Anda memiliki mutex bisa berubah-ubah adalah contoh kanonik yang baik.


4

Penggunaan Anda terhadapnya bukan peretasan, meskipun seperti banyak hal dalam C ++, bisa berubah bisa menjadi peretas untuk programmer yang malas yang tidak ingin kembali lagi dan menandai sesuatu yang seharusnya tidak dianggap sebagai non-const.


3

Gunakan "bisa berubah" ketika untuk hal-hal yang LOGICALLY stateless kepada pengguna (dan karenanya harus memiliki "const" getter di API kelas publik) tetapi BUKAN stateless dalam IMPLEMENTASI yang mendasarinya (kode dalam .cpp Anda).

Kasus-kasus yang paling sering saya gunakan adalah inisialisasi malas dari negara-kurang "data lama polos" anggota. Yaitu, ini ideal dalam kasus-kasus sempit ketika anggota tersebut mahal untuk membangun (prosesor) atau membawa-bawa (memori) dan banyak pengguna objek tidak akan pernah meminta mereka. Dalam situasi itu Anda ingin konstruksi yang malas di bagian belakang untuk kinerja, karena 90% dari objek yang dibangun tidak akan perlu untuk membangun sama sekali, namun Anda masih perlu menyajikan API stateless yang benar untuk konsumsi publik.


2

Mutable mengubah arti dari constconst bitwise ke const const untuk kelas.

Ini berarti bahwa kelas-kelas dengan anggota yang bisa berubah lebih lama menjadi const bitwise dan tidak akan lagi muncul di bagian read-only dari executable.

Selain itu, ia memodifikasi pemeriksaan tipe dengan memungkinkan constfungsi anggota untuk mengubah anggota yang dapat berubah tanpa menggunakan const_cast.

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

Lihat jawaban lain untuk perincian lebih lanjut, tetapi saya ingin menyoroti bahwa ini bukan hanya untuk tipe-aman dan itu mempengaruhi hasil kompilasi.


1

Dalam beberapa kasus (seperti iterator yang dirancang dengan buruk), kelas perlu menyimpan hitungan atau nilai insidental lainnya, yang tidak benar-benar mempengaruhi "keadaan" utama kelas. Ini paling sering di mana saya melihat bisa berubah digunakan. Tanpa bisa berubah, Anda akan dipaksa untuk mengorbankan seluruh keteguhan desain Anda.

Rasanya seperti hack sebagian besar waktu untuk saya juga. Berguna dalam situasi yang sangat sedikit.


1

Contoh klasik (seperti yang disebutkan dalam jawaban lain) dan satu-satunya situasi saya telah melihat mutablekata kunci yang digunakan sejauh ini, adalah untuk caching hasil dari Getmetode yang rumit , di mana cache diimplementasikan sebagai anggota data kelas dan bukan sebagai variabel statis dalam metode (untuk alasan berbagi antara beberapa fungsi atau kebersihan polos).

Secara umum, alternatif untuk menggunakan mutablekata kunci biasanya variabel statis dalam metode atau const_casttriknya.

Penjelasan terperinci lainnya ada di sini .


1
Saya belum pernah mendengar tentang menggunakan anggota statis sebagai alternatif umum untuk anggota yang bisa berubah. Dan const_casthanya untuk ketika Anda tahu (atau telah dijamin) bahwa sesuatu tidak akan diubah (misalnya ketika mengganggu perpustakaan C) atau ketika Anda tahu itu tidak dinyatakan const. Yaitu, memodifikasi hasil variabel const-casted dalam perilaku yang tidak terdefinisi.
Sebastian Mach

1
@phornel Dengan "variabel statis" yang saya maksud adalah variabel otomatis statis dalam metode ini (yang tetap digunakan untuk panggilan). Dan const_castdapat digunakan untuk memodifikasi anggota kelas dalam suatu constmetode, yang saya sebut ...
Daniel Hershcovich

1
Itu tidak jelas bagi saya, karena Anda menulis "secara umum" :) Berkenaan dengan memodifikasi melalui const_cast, seperti yang dikatakan ini hanya diperbolehkan ketika objek tidak dideklarasikan const. Misalnya const Frob f; f.something();, dengan void something() const { const_cast<int&>(m_foo) = 2;hasil dalam perilaku yang tidak terdefinisi.
Sebastian Mach

1

Dapat berubah bisa berguna ketika Anda menimpa fungsi virtual const dan ingin memodifikasi variabel anggota kelas anak Anda dalam fungsi itu. Dalam sebagian besar kasus, Anda tidak ingin mengubah antarmuka kelas dasar, jadi Anda harus menggunakan variabel anggota yang dapat diubah-ubah sendiri.


1

Kata kunci yang dapat berubah sangat berguna saat membuat bertopik untuk tujuan pengujian kelas. Anda dapat mematikan fungsi const dan masih dapat meningkatkan (bisa berubah) penghitung atau fungsi pengujian apa pun yang telah Anda tambahkan ke rintisan Anda. Ini menjaga antarmuka kelas stubbed tetap utuh.


0

Salah satu contoh terbaik di mana kita menggunakan bisa berubah adalah, dalam salinan yang dalam. dalam copy constructor kami kirim const &objsebagai argumen. Jadi objek baru yang dibuat akan bertipe konstan. Jika kita ingin mengubah (kebanyakan kita tidak akan berubah, dalam kasus yang jarang kita dapat mengubah) anggota dalam objek const yang baru dibuat ini kita perlu mendeklarasikannya sebagai mutable.

mutablekelas penyimpanan hanya dapat digunakan pada anggota data kelas non-statis non-kelas. Anggota data yang dapat berubah dari suatu kelas dapat dimodifikasi bahkan jika itu adalah bagian dari objek yang dinyatakan sebagai const.

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

Dalam contoh di atas, kita dapat mengubah nilai variabel anggota xmeskipun itu merupakan bagian dari objek yang dinyatakan sebagai const. Ini karena variabel xdinyatakan sebagai bisa berubah. Tetapi jika Anda mencoba mengubah nilai variabel anggota y, kompiler akan melempar kesalahan.


-1

Kata kunci yang sangat 'berubah-ubah' sebenarnya adalah kata kunci yang dilindungi undang-undang. Sering kali kata kunci tersebut digunakan untuk memvariasikan nilai variabel konstan. Jika Anda ingin memiliki beberapa nilai konstanta, gunakan kata kunci yang dapat berubah-ubah.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.