Il y a quelque temps, je suis tombé sur un code marquant une variable membre d'une classe avec le mot clé mutable
. Autant que je sache, cela vous permet simplement de modifier une variable dans une méthode const
:
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 autre chose que ce que l'on voit? Depuis, j'ai utilisé cette technique dans une classe, marquant un boost::mutex
comme étant mutable, permettant ainsi à des fonctions const
de le verrouiller pour des raisons de sécurité du thread, mais, pour être honnête, cela ressemble à un peu.
Il permet la différenciation de const au niveau du bit et de const logique. Le terme logique signifie qu'un objet ne change pas d'une manière visible via l'interface publique, comme dans l'exemple de verrouillage. Un autre exemple serait une classe qui calcule une valeur lors de la première demande et met en cache le résultat.
Puisque c ++ 11 mutable
peut être utilisé sur un lambda pour indiquer que les éléments capturés par la valeur sont modifiables (ils 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
Le mot clé mutable
permet de percer le voile const
que vous drapez sur vos objets. Si vous avez une référence const ou un pointeur sur un objet, vous ne pouvez pas modifier cet objet , sauf quand et comment il est marqué mutable
.
Avec votre référence ou votre pointeur const
, vous devez:
const
.L'exception mutable
permet ainsi d'écrire ou de définir des membres de données marqués mutable
. C'est la seule différence visible de l'extérieur.
En interne, les méthodes const
visibles par vous peuvent également écrire sur les membres de données marqués mutable
. Essentiellement, le voile de const est complètement percé. Il appartient au concepteur de l’API de s’assurer que mutable
ne détruit pas le concept const
et n’est utilisé que dans des cas spéciaux utiles. Le mot clé mutable
est utile car il indique clairement les membres de données soumis à ces cas particuliers.
En pratique, vous pouvez utiliser const
de manière obsessionnelle tout au long de votre base de code (vous voulez essentiellement "infecter" votre base de code avec le const
"maladie"). Dans ce monde, les pointeurs et les références sont const
à quelques rares exceptions près, ce qui permet d'obtenir un code plus facile à raisonner et à comprendre. Pour une digression intéressante, recherchez "transparence référentielle".
Sans le mot clé mutable
, vous serez éventuellement obligé d'utiliser const_cast
pour gérer les divers cas spéciaux utiles qu'il permet (mise en cache, comptage des références, débogage des données, etc.). Malheureusement, const_cast
est nettement plus destructeur que mutable
car il oblige le client API à détruire la protection const
de les objets qu'il utilise. De plus, il provoque une destruction généralisée de const
: const_cast
un pointeur ou une référence const, ce qui permet un accès en écriture et en accès de méthode sans entrave aux membres visibles. En revanche, mutable
requiert que le concepteur d'API exerce un contrôle fin sur les exceptions const
, et ces exceptions sont généralement masquées dans les méthodes const
opérant sur des données privées.
(Remarque: je fais référence à des données et à une méthode (plusieurs fois). plusieurs fois. Je parle de membres marqués comme publics ou privés ou protégés, ce qui est un type de protection d'objet totalement différent. ici .)
Votre utilisation avec boost :: mutex correspond exactement à ce mot-clé. Une autre utilisation est la mise en cache interne des résultats pour accélérer l'accès.
Fondamentalement, "mutable" s'applique à tout attribut de classe qui n'affecte pas l'état visible de l'objet de l'extérieur.
Dans l'exemple de code de votre question, mutable pourrait ne pas être approprié si la valeur de done_ affecte l'état externe, cela dépend de ce qu'il y a dans le ...; partie.
Mutable permet de marquer des attributs spécifiques comme étant modifiables depuis les méthodes const
. 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 d'utiliser mutable
.
http://www.highprogrammer.com/alan/rants/mutable.html
Donc, si la folie ci-dessus n'est pas à quoi sert Mutable, à quoi ça sert? Voici le cas subtil: mutable correspond au cas où un objet est logiquement constant, mais doit en pratique changer. Ces cas sont rares, mais ils existent.
Parmi les exemples cités par l'auteur figurent la mise en cache et les variables de débogage temporaires.
C'est utile dans les situations où vous avez caché un état interne tel qu'un cache. Par exemple:
class HashTable { ... public: recherche de chaîne (clé de chaîne) const { if (key == lastKey) renvoie lastValue; chaîne value = lookupInternal (clé); lastKey = clé; lastValue = value; renvoyer une valeur; } private: chaîne mutable lastKey, lastValue; };
Et ensuite, vous pouvez avoir un objet const HashTable
utiliser sa méthode lookup()
, qui modifie le cache interne.
mutable
existe comme vous le supposez pour permettre de modifier des 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 pourriez avoir vraiment besoin de modifier certains états d'objet d'une manière qui ne n'affecte pas sa fonctionnalité correcte.
Le mot-clé peut agir comme une indication pour le compilateur - un compilateur théorique pourrait placer un objet constant (tel qu'un global) en mémoire qui a été marqué en lecture seule. La présence de mutable
suggère que cela ne devrait pas être fait.
Voici quelques raisons valables pour déclarer et utiliser des données mutables:
mutable boost::mutex
est parfaitement raisonnable.Eh bien, c'est ce que ça fait. Je l'utilise pour les membres modifiés par des méthodes qui ne logiquement modifient 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 utiliser ceci avec précaution - les problèmes de simultanéité sont un gros problème, car un appelant peut supposer qu'ils sont thread-safe s'ils n'utilisent que des méthodes const
. Et bien sûr, modifier les données mutable
ne devrait pas changer le comportement de l’objet de manière significative, ce qui pourrait être violé par l’exemple que j’avais donné si, par exemple, il était prévu que les modifications écrites sur le disque soient modifiées. visible immédiatement par l'application.
Mutable est utilisé lorsque vous avez une variable dans la classe qui est utilisée uniquement 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 à la fois le compilateur et le lecteur qu'il est sûr et qu'il est prévu qu'une variable membre puisse être modifiée dans une fonction membre const.
Votre utilisation n’est pas un hack, bien que, comme beaucoup de choses en C++, mutable puisse être un hack pour un programmeur paresseux qui ne veut pas y aller tout le chemin en arrière et marquer quelque chose qui ne devrait pas être const comme non-const.
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 de mutable mutable est un bon exemple canonique.
Utilisez "mutable" pour les objets qui sont logiquement sans état pour l'utilisateur (et qui devraient donc avoir des getters "const" dans les API de la classe publique) mais qui ne sont PAS sans état dans IMPLEMENTATION sous-jacent (le code dans votre .cpp).
Les cas que 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 les plus étroits lorsque la construction (processeur) ou le stockage (mémoire) de ces membres sont coûteux et que de nombreux utilisateurs de l’objet ne les demandent jamais. Dans cette situation, vous voulez une performance paresseuse sur le back-end pour des performances optimales, car 90% des objets construits n'auront jamais besoin de les construire, mais vous devez néanmoins présenter l'API sans état appropriée pour la consommation publique.
Mutable change le sens de const
de const au niveau du bit en const logique pour la classe.
Cela signifie que les classes avec des membres mutables sont plus longues et qu'elles n'apparaissent plus dans les sections en lecture seule de l'exécutable.
De plus, il modifie la vérification de type en permettant à const
fonctions membres de modifier 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 simplement pour la sécurité du type et que cela affecte le résultat compilé.
L’un des meilleurs exemples d’utilisation de mutable est la copie en profondeur. dans le constructeur de copie, nous envoyons const &obj
comme argument. Donc, le nouvel objet créé sera de type constant. Si nous voulons changer (nous ne changerons généralement pas, mais dans de rares cas, nous pouvons changer) les membres de cet objet const nouvellement créé, nous devons le déclarer comme mutable
.
mutable
La classe de stockage ne peut être utilisée que sur un membre de données non statique non constante 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 modifier la valeur de la variable membre x
bien qu'elle fasse partie d'un objet déclaré en tant que const. En effet, la variable x
est déclarée mutable. Mais si vous essayez de modifier la valeur de la variable membre y
, le compilateur lève une erreur.
Le mot clé mutable est très utile lors de la création de stubs à des fins de test de classe. Vous pouvez remplacer une fonction const tout en augmentant le nombre de compteurs (modifiables) ou la fonctionnalité de test que vous avez ajoutée à votre talon. Cela permet de conserver l’interface de la classe stubbed.
Dans certains cas (comme des itérateurs mal conçus), la classe doit garder un compte ou une autre valeur incidente, cela n'affectant pas vraiment le "grand" état de la classe. C'est le plus souvent où je vois mutable utilisé. Sans mutable, vous seriez obligé de sacrifier l'intégralité de votre conception.
La plupart du temps, cela ressemble à un bidouillage. Utile dans très très peu de situations.
L'exemple classique (comme mentionné dans d'autres réponses) et la seule situation dans laquelle j'ai vu le mot clé mutable
utilisé jusqu'ici concerne la mise en cache du résultat d'une méthode Get
compliquée, où le cache est implémenté de la manière suivante: une donnée membre de la classe et non pas une 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 mot clé mutable
sont généralement une variable statique dans la méthode ou l’astuce const_cast
.
Une autre explication détaillée est dans ici .
Le mutable peut être pratique lorsque vous substituez 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.
Le mot-clé 'mutable' est en fait un mot-clé réservé. Il est souvent utilisé pour faire varier la valeur de la variable constante. Si vous souhaitez avoir plusieurs valeurs d'un constsnt, utilisez le mot-clé mutable.
//Prototype
class tag_name{
:
:
mutable var_name;
:
:
};