Le mot-clé 'mutable' a-t-il un but autre que de permettre à la variable d'être modifiée par une fonction const?

527

Il y a quelque temps, je suis tombé sur du code qui marquait une variable membre d'une classe avec le mutablemot - clé. Pour autant que je sache, cela vous permet simplement de modifier une variable dans une constméthode:

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

Est-ce la seule utilisation de ce mot-clé ou y a-t-il plus que ce qui semble évident? Depuis, j'ai utilisé cette technique dans une classe, marquant une boost::mutexfonction mutable permettant constde la verrouiller pour des raisons de sécurité des threads, mais, pour être honnête, cela ressemble à un hack.

Rob
la source
2
Une question cependant, si vous ne modifiez rien, pourquoi avez-vous besoin d'utiliser un mutex en premier lieu? Je veux juste comprendre cela.
Misgevolution
@Misgevolution vous modifiez quelque chose, vous contrôlez juste qui / comment peut faire la modification via const. Un exemple vraiment naïf, imaginez que si je donne seulement des poignées non const à des amis, les ennemis obtiennent une poignée const. Les amis peuvent modifier, les ennemis non.
iheanyi
1
Remarque: voici un excellent exemple d'utilisation du mot clé mutable: stackoverflow.com/questions/15999123/…
Gabriel Staples
Je souhaite qu'il puisse être utilisé pour remplacer const(des types), donc je n'ai pas à le faire class A_mutable{}; using A = A_mutable const; mutable_t<A> a;:, si je veux const-by-default, c'est-à-dire mutable A a;(mutable explicite) et A a;(const implicite).
alfC

Réponses:

351

Il permet la différenciation de const au niveau du bit et de const logique. La const logique est lorsqu'un objet ne change pas d'une manière visible à travers l'interface publique, comme votre exemple de verrouillage. Un autre exemple serait une classe qui calcule une valeur la première fois qu'elle est demandée et met en cache le résultat.

Puisque c ++ 11 mutablepeut être utilisé sur un lambda pour indiquer que les choses capturées par valeur sont modifiables (elles ne le sont pas par défaut):

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
KeithB
la source
52
'mutable' n'affecte en rien la constance bit à bit / logique. C ++ n'est que const au niveau du bit et le mot clé 'mutable' peut être utilisé pour exclure les membres de cette vérification. Il n'est pas possible d'obtenir une const 'logique' en C ++ autrement que via des abstractions (par exemple SmartPtrs).
Richard Corden
111
@ Richard: vous manquez le point. Il n'y a pas de mot clé "const logique", vrai, c'est plutôt une différenciation conceptuelle que le programmeur fait pour décider quels membres doivent être exclus en étant rendus mutables, sur la base d'une compréhension de ce qui constitue l'état logique observable de l'objet.
Tony Delroy
6
@ajay Oui, c'est tout l'intérêt de marquer une variable membre comme mutable, pour lui permettre d'être modifiée dans les objets const.
KeithB
6
Pourquoi faut-il mutable sur lambdas? Ne suffirait-il pas de capturer une variable par référence?
Giorgio
11
@Giorgio: La différence est que le modifié xdans le lambda reste dans le lambda, c'est-à-dire que la fonction lambda ne peut modifier que sa propre copie de x. Le changement n'est pas visible à l'extérieur, l'original xest toujours inchangé. Considérez que les lambdas sont implémentés en tant que classes de foncteurs; les variables capturées correspondent aux variables membres.
Sebastian Mach
138

Le mutablemot-clé est un moyen de percer le constvoile que vous drapez sur vos objets. Si vous avez une référence const ou un pointeur vers un objet, vous ne pouvez en aucun cas modifier cet objet sauf quand et comment il est marqué mutable.

Avec votre constréférence ou votre pointeur, vous êtes contraint de:

  • accès en lecture uniquement aux membres de données visibles
  • autorisation d'appeler uniquement les méthodes marquées comme const.

Grâce à l' mutableexception, vous pouvez désormais écrire ou définir des membres de données marqués mutable. C'est la seule différence visible de l'extérieur.

En interne, les constméthodes visibles par vous peuvent également écrire sur les membres de données marqués mutable. Essentiellement, le voile const est percé de manière complète. Il appartient entièrement au concepteur d'API de s'assurer que mutablecela ne détruit pas le constconcept et n'est utilisé que dans des cas spéciaux utiles. Le mutablemot-clé est utile car il marque clairement les membres de données qui sont soumis à ces cas particuliers.

En pratique, vous pouvez utiliser de manière constobsessionnelle tout au long de votre base de code (vous voulez essentiellement "infecter" votre base de code avec leconst "maladie"). Dans ce monde, les pointeurs et les références sont constà de très rares exceptions près, produisant un code plus facile à raisonner et à comprendre. Pour une digression intéressante, recherchez "transparence référentielle".

Sans le mutablemot - clé, vous serez éventuellement contraint d'utiliser const_castpour gérer les différents cas spéciaux utiles qu'il permet (mise en cache, comptage de références, données de débogage, etc.). Malheureusement, il const_castest beaucoup plus destructeur que mutableparce qu'il oblige le client API à détruire la constprotection des objets qu'il utilise. De plus, il provoque une constdestruction généralisée : const_castun pointeur ou une référence const permet une écriture et une méthode d'appel sans entrave donnant accès aux membres visibles. En revanche, mutablele concepteur d'API doit exercer un contrôle fin sur les constexceptions, et généralement ces exceptions sont masquées dansconst méthodes fonctionnant sur des données privées.

(NB je me réfère à la visibilité des données et des méthodes à quelques reprises. Je parle des membres marqués comme public vs privé ou protégé qui est un type totalement différent de protection d'objet discuté ici .)

Dan L
la source
8
De plus, l'utilisation const_castpour modifier une partie d'un constobjet donne un comportement indéfini.
Brian
Je ne suis pas d'accord car cela oblige le client API à détruire la protection const des objets . Si vous utilisiez const_castpour implémenter la mutation des variables membres dans une constméthode, vous ne demanderiez pas au client de faire le cast - vous le feriez dans la méthode en const_casting this. Fondamentalement, il vous permet de contourner la constance sur des membres arbitraires sur un site d'appels spécifique , tout mutableen vous permettant de supprimer const sur un membre spécifique sur tous les sites d'appels. Ce dernier est généralement ce que vous voulez pour une utilisation typique (mise en cache, statistiques), mais parfois le const_cast correspond au modèle.
BeeOnRope
1
Le const_castmodèle s'adapte mieux dans certains cas, comme lorsque vous souhaitez modifier temporairement un membre, puis le restaurer (à peu près comme boost::mutex). La méthode est logiquement constante car l'état final est le même que celui initial, mais vous souhaitez effectuer ce changement transitoire. const_castpeut être utile car il vous permet de supprimer const spécifiquement dans cette méthode si vous la mutation sera annulée, mais mutablene serait pas aussi approprié car il supprimerait la protection const de toutes les méthodes, qui ne suivent pas nécessairement toutes le "do" , annuler "modèle.
BeeOnRope
2
Le placement possible de l' objet défini par const dans la mémoire morte (plus généralement, la mémoire marquée en lecture seule) et le langage standard associé qui permet cela font cependant const_castune bombe à retardement possible. mutablen'a pas un tel problème car de tels objets n'ont pas pu être placés en mémoire morte.
BeeOnRope
75

Votre utilisation avec boost :: mutex est exactement à quoi ce mot-clé est destiné. Une autre utilisation est la mise en cache des résultats internes pour accélérer l'accès.

Fondamentalement, «mutable» s'applique à tout attribut de classe qui n'affecte pas l'état visible de l'extérieur de l'objet.

Dans l'exemple de code dans votre question, mutable peut être inapproprié si la valeur de done_ affecte l'état externe, cela dépend de ce qui est dans le ...; partie.

Frank Szczerba
la source
35

Mutable sert à marquer un attribut spécifique comme modifiable à partir de constméthodes internes . C'est son seul but. Réfléchissez bien avant de l'utiliser, car votre code sera probablement plus propre et plus lisible si vous modifiez la conception plutôt que de l'utilisermutable .

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

Donc, si la folie ci-dessus n'est pas à quoi sert le mutable, à quoi sert-elle? Voici le cas subtil: mutable est le cas où un objet est logiquement constant, mais en pratique doit changer. Ces cas sont rares, mais ils existent.

Les exemples que l'auteur donne incluent la mise en cache et les variables de débogage temporaires.

John Millikin
la source
2
Je pense que ce lien donne le meilleur exemple d'un scénario où mutable est utile. Il semble presque qu'ils sont exclusivement utilisés pour le débogage. (par utilisation correcte)
enthousiasticgeek
L'utilisation de mutablepeut rendre le code plus lisible et plus propre. Dans l'exemple suivant, le readpeut être constcomme prévu. `m_mutex mutable; Container m_container; void add (Item item) {Lockguard lock (m_mutex); m_container.pushback (élément); } Élément read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Th. Thielemann
Il existe un cas d'utilisation extrêmement populaire: le nombre de références.
Seva Alekseyev
33

Il est utile dans les situations où vous avez caché un état interne tel qu'un cache. Par exemple:

classe HashTable
{
...
Publique:
    recherche de chaîne (clé de chaîne) const
    {
        if (key == lastKey)
            return lastValue;

        valeur de chaîne = lookupInternal (clé);

        lastKey = clé;
        lastValue = valeur;

        valeur de retour;
    }

privé:
    chaîne mutable lastKey, lastValue;
};

Et puis, vous pouvez faire en sorte qu'un const HashTableobjet utilise toujours sa lookup()méthode, qui modifie le cache interne.

Adam Rosenfield
la source
9

mutable existe comme vous le déduisez pour permettre de modifier les données dans une fonction par ailleurs constante.

L'intention est que vous puissiez avoir une fonction qui "ne fait rien" à l'état interne de l'objet, et donc vous marquez la fonction const, mais vous devrez peut-être vraiment modifier certains des objets de manière à ne pas affecter son bon Fonctionnalité.

Le mot-clé peut agir comme un indice pour le compilateur - un compilateur théorique pourrait placer un objet constant (tel qu'un global) dans la mémoire qui a été marqué en lecture seule. La présence d' mutableindices que cela ne devrait pas être fait.

Voici quelques raisons valables pour déclarer et utiliser des données mutables:

  • Sécurité des fils. Déclarer un mutable boost::mutexest parfaitement raisonnable.
  • Statistiques. Compter le nombre d'appels à une fonction, compte tenu de tout ou partie de ses arguments.
  • Mémorisation. Calculer une réponse coûteuse, puis la stocker pour référence future plutôt que de la recalculer à nouveau.
Lloyd
la source
2
Bonne réponse, à l'exception des commentaires concernant mutable étant un "indice". Cela donne l'impression que le membre mutable ne sera parfois pas mutable si le compilateur place l'objet dans la ROM. Le comportement du mutable est bien défini.
Richard Corden
2
En plus de placer un objet const dans la mémoire morte, le compilateur peut également décider d'optimiser les appels const fucntion hors d'une boucle, par exemple. Un compteur de statistiques mutable dans une fonction autrement const permettra toujours une telle optimisation (et ne comptera qu'un seul appel) au lieu d'empêcher l'optimisation juste pour le compte de plus d'appels.
Hagen von Eitzen
@HagenvonEitzen - Je suis presque sûr que c'est incorrect. Un compilateur ne peut pas sortir des fonctions d'une boucle à moins qu'il ne prouve qu'il n'y a pas d'effets secondaires. Cette preuve implique généralement d'inspecter réellement la mise en œuvre de la fonction (souvent après qu'elle soit insérée) et de ne pas s'en remettre const(et une telle inspection réussira ou échouera indépendamment de constou mutable). Déclarer simplement la fonction constne suffit pas: une constfonction est libre d'avoir des effets secondaires tels que la modification d'une variable globale ou quelque chose passée à la fonction, donc ce n'est pas une garantie utile pour cette preuve.
BeeOnRope
Maintenant, certains compilateurs ont des extensions spéciales telles que _attribute __ ((const)) et __attribute __ ((pure)) de gcc, qui _do ont de tels effets , mais ce n'est que tangentiellement lié au constmot clé en C ++.
BeeOnRope
8

Eh bien, oui, c'est ce que ça fait. Je l'utilise pour les membres modifiés par des méthodes qui ne changent pas logiquement l'état d'une classe - par exemple, pour accélérer les recherches en implémentant un 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;
};

Maintenant, vous devez l'utiliser avec précaution - les problèmes de concurrence sont une grande préoccupation, car un appelant peut supposer qu'ils sont sûrs pour les threads uniquement en utilisant des constméthodes. Et bien sûr, la modification des mutabledonnées ne devrait pas changer le comportement de l'objet de manière significative, quelque chose qui pourrait être violé par l'exemple que j'ai donné si, par exemple, on s'attendait à ce que les modifications écrites sur le disque soient immédiatement visibles par l'application .

Shog9
la source
6

Mutable est utilisé lorsque vous avez une variable à l'intérieur de la classe qui n'est utilisée que dans cette classe pour signaler des choses comme par exemple un mutex ou un verrou. Cette variable ne modifie pas le comportement de la classe, mais est nécessaire pour implémenter la sécurité des threads de la classe elle-même. Ainsi, sans "mutable", vous ne pourriez pas avoir de fonctions "const" car cette variable devra être modifiée dans toutes les fonctions disponibles pour le monde extérieur. Par conséquent, mutable a été introduit afin de rendre une variable membre accessible en écriture même par une fonction const.

Le mutable spécifié informe le compilateur et le lecteur qu'il est sûr et attendu qu'une variable membre peut être modifiée dans une fonction membre const.

mkschreder
la source
4

mutable est principalement utilisé sur un détail d'implémentation de la classe. L'utilisateur de la classe n'a pas besoin de le savoir, donc la méthode qu'il pense "devrait" être const peut l'être. Votre exemple d'avoir un mutex mutable est un bon exemple canonique.

Greg Rogers
la source
4

Votre utilisation de celui-ci n'est pas un hack, bien que comme beaucoup de choses en C ++, mutable peut être piraté pour un programmeur paresseux qui ne veut pas revenir en arrière et marquer quelque chose qui ne devrait pas être const comme non-const.

JohnMcG
la source
3

Utilisez "mutable" lorsque pour des choses qui sont LOGICALEMENT sans état pour l'utilisateur (et doivent donc avoir des getters "const" dans les API de la classe publique) mais ne sont PAS sans état dans la MISE EN ŒUVRE sous-jacente (le code dans votre .cpp).

Les cas où je l'utilise le plus souvent sont l'initialisation paresseuse de membres "sans données anciennes" sans état. À savoir, il est idéal dans les cas étroits où de tels membres sont coûteux à construire (processeur) ou à transporter (mémoire) et de nombreux utilisateurs de l'objet ne les demanderont jamais. Dans cette situation, vous voulez une construction paresseuse à l'arrière pour les performances, car 90% des objets construits n'auront jamais besoin de les construire, mais vous devez toujours présenter la bonne API sans état pour la consommation publique.

Zack Yezek
la source
2

Mutable change la signification de constconst au niveau du bit en const logique pour la classe.

Cela signifie que les classes avec des membres mutables sont plus constantes au niveau du bit et n'apparaissent plus dans les sections en lecture seule de l'exécutable.

De plus, il modifie la vérification de type en permettant constaux fonctions membres de changer les membres mutables sans utiliser 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.
}

Voir les autres réponses pour plus de détails, mais je voulais souligner que ce n'est pas seulement pour la sécurité de type et qu'il affecte le résultat compilé.

Kevin Cox
la source
1

Dans certains cas (comme les itérateurs mal conçus), la classe doit garder un décompte ou une autre valeur incidente, qui n'affecte pas vraiment le "principal" état de la classe. C'est le plus souvent où je vois mutable utilisé. Sans mutable, vous seriez obligé de sacrifier toute la constance de votre conception.

Cela ressemble aussi à un hack la plupart du temps pour moi. Utile dans très très peu de situations.

Joe Schneider
la source
1

L'exemple classique (comme mentionné dans d'autres réponses) et la seule situation dans laquelle j'ai vu le mutablemot - clé utilisé jusqu'à présent est la mise en cache du résultat d'une Getméthode compliquée , où le cache est implémenté en tant que membre de données de la classe et non en tant que variable statique dans la méthode (pour des raisons de partage entre plusieurs fonctions ou de propreté absolue).

En général, les alternatives à l'utilisation du mutablemot-clé sont généralement une variable statique dans la méthode ou l' const_castastuce.

Une autre explication détaillée se trouve ici .

Daniel Hershcovich
la source
1
Je n'ai jamais entendu parler de l'utilisation de membres statiques comme alternative générale aux membres mutables. Et ce const_castn'est que lorsque vous savez (ou avez été garanti) que quelque chose ne sera pas modifié (par exemple lors d'interférences avec les bibliothèques C) ou lorsque vous savez qu'il n'a pas été déclaré const. C'est-à-dire que la modification d'une variable const const castée entraîne un comportement indéfini.
Sebastian Mach
1
@phresnel Par "variables statiques", je voulais dire des variables automatiques statiques dans la méthode (qui restent à travers les appels). Et const_castpeut être utilisé pour modifier un membre de classe dans une constméthode, ce à quoi je faisais référence ...
Daniel Hershcovich
1
Ce n'était pas clair pour moi, comme vous l'avez écrit "en général" :) En ce qui concerne la modification à travers const_cast, comme dit, cela n'est autorisé que lorsque l'objet n'a pas été déclaré const. Par exemple const Frob f; f.something();, avec des void something() const { const_cast<int&>(m_foo) = 2;résultats dans un comportement indéfini.
Sebastian Mach
1

Le mutable peut être pratique lorsque vous remplacez une fonction virtuelle const et que vous souhaitez modifier votre variable membre de classe enfant dans cette fonction. Dans la plupart des cas, vous ne voudriez pas modifier l'interface de la classe de base, vous devez donc utiliser votre propre variable membre mutable.

Saurabh
la source
1

Le mot clé mutable est très utile lors de la création de stubs à des fins de test de classe. Vous pouvez stub une fonction const et toujours être en mesure d'augmenter les compteurs (mutables) ou n'importe quelle fonctionnalité de test que vous avez ajoutée à votre stub. Cela conserve l'interface de la classe tronquée intacte.

Martin G
la source
0

L'un des meilleurs exemples où nous utilisons mutable est, en copie profonde. dans le constructeur de copie, nous envoyons const &objcomme argument. Le nouvel objet créé sera donc de type constant. Si nous voulons changer (surtout nous ne changerons pas, dans de rares cas nous pouvons changer) les membres de cet objet const nouvellement créé, nous devons le déclarer comme mutable.

mutableLa classe de stockage ne peut être utilisée que sur un membre de données non statique et non const d'une classe. Le membre de données mutable d'une classe peut être modifié même s'il fait partie d'un objet déclaré comme 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

Dans l'exemple ci-dessus, nous pouvons changer la valeur de la variable membre xbien qu'elle fasse partie d'un objet qui est déclaré const. En effet, la variable xest déclarée comme mutable. Mais si vous essayez de modifier la valeur de la variable membre y, le compilateur lèvera une erreur.

Venkatakrishna Kalepalli
la source
-1

Le mot-clé même "mutable" est en fait un mot-clé réservé. Souvent, il est utilisé pour faire varier la valeur d'une variable constante. Si vous voulez avoir plusieurs valeurs d'une constante, utilisez le mot-clé mutable.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Rajdeep Rathore
la source