¿La palabra clave 'mutable' tiene otro propósito que no sea permitir que la variable sea modificada por una función const?

527

Hace un tiempo me encontré con un código que marcaba una variable miembro de una clase con la mutablepalabra clave. Por lo que puedo ver, simplemente le permite modificar una variable en un constmétodo:

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

¿Es este el único uso de esta palabra clave o hay más de lo que parece? Desde entonces, he usado esta técnica en una clase, marcando boost::mutexcomo mutable permitiendo que las constfunciones lo bloqueen por razones de seguridad de subprocesos, pero, para ser honesto, se siente como un truco.

Robar
fuente
2
Una pregunta, sin embargo, si no está modificando nada, ¿por qué necesita usar un mutex en primer lugar? Solo quiero entender esto.
Misgevolution
@Misgevolution estás modificando algo, solo estás controlando quién / cómo puede hacer la modificación a través de const. Un ejemplo realmente ingenuo, imagina que si solo doy manijas no constantes a amigos, los enemigos obtienen una manilla constante. Los amigos pueden modificar, los enemigos no pueden.
iheanyi
1
Nota: aquí hay un gran ejemplo del uso de la palabra clave mutable: stackoverflow.com/questions/15999123/…
Gabriel Staples
Desearía poder usarlo para anular const(de tipos), por lo que no tengo que hacer esto: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;si quiero const por defecto, es decir mutable A a;(mutable explícito) y A a;(const implícito).
AlfC

Respuestas:

351

Permite la diferenciación de const bit a bit y const lógico. Const lógico es cuando un objeto no cambia de manera visible a través de la interfaz pública, como su ejemplo de bloqueo. Otro ejemplo sería una clase que calcula un valor la primera vez que se solicita y almacena en caché el resultado.

Dado que c ++ 11 mutablese puede usar en una lambda para denotar que las cosas capturadas por valor son modificables (no lo son por defecto):

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
fuente
52
'mutable' no afecta en absoluto a la constidad bit a bit / lógica. C ++ es solo bit const y la palabra clave 'mutable' puede usarse para excluir miembros de esta comprobación. No es posible lograr const 'lógico' en C ++ que no sea a través de abstracciones (por ejemplo, SmartPtrs).
Richard Corden
111
@ Richard: te estás perdiendo el punto. No existe una palabra clave "const lógico", es cierto, más bien, es una diferenciación conceptual que el programador hace para decidir qué miembros deben excluirse al hacerse mutables, en base a una comprensión de lo que constituye el estado lógico observable del objeto.
Tony Delroy
66
@ajay Sí, ese es el punto de marcar una variable miembro como mutable, para permitir que se cambie en objetos constantes.
KeithB
66
¿Por qué uno necesita mutable en lambdas? ¿No sería suficiente capturar variables por referencia?
Giorgio
11
@Giorgio: La diferencia es que el modificado xdentro del lambda permanece dentro del lambda, es decir, la función lambda solo puede modificar su propia copia de x. El cambio no es visible en el exterior, el original xaún no ha cambiado. Considere que las lambdas se implementan como clases functor; Las variables capturadas corresponden a las variables miembro.
Sebastian Mach
138

La mutablepalabra clave es una forma de perforar el constvelo que cubre con sus objetos. Si tiene una referencia constante o puntero a un objeto, no puede modificar ese objeto de ninguna manera, excepto cuándo y cómo está marcado mutable.

Con su constreferencia o puntero, está obligado a:

  • solo acceso de lectura para cualquier miembro de datos visible
  • permiso para llamar solo a métodos que están marcados como const.

La mutableexcepción hace que ahora pueda escribir o establecer miembros de datos que están marcados mutable. Esa es la única diferencia externamente visible.

Internamente, los constmétodos que son visibles para usted también pueden escribir en los miembros de datos que están marcados mutable. Esencialmente, el velo constante está perforado de manera integral. Depende completamente del diseñador de API asegurarse de que mutableno destruya el constconcepto y solo se use en casos especiales útiles. La mutablepalabra clave ayuda porque marca claramente a los miembros de datos que están sujetos a estos casos especiales.

En la práctica, puede usarlo constobsesivamente en toda su base de código (esencialmente desea "infectar" su base de código con la const"enfermedad"). En este mundo, los punteros y las referencias son constcon muy pocas excepciones, produciendo código que es más fácil de razonar y entender. Para una digresión interesante, busque "transparencia referencial".

Sin la mutablepalabra clave, eventualmente se verá obligado a usar const_castpara manejar los diversos casos especiales útiles que permite (almacenamiento en caché, recuento de referencias, datos de depuración, etc.). Desafortunadamente, const_castes significativamente más destructivo que mutableporque obliga al cliente API a destruir la constprotección de los objetos que está utilizando. Además, causa una constdestrucción generalizada : el uso const_castde un puntero o referencia constante permite la escritura sin restricciones y el método de acceso a miembros visibles. Por el contrario, mutablerequiere que el diseñador de la API ejerza un control preciso sobre las constexcepciones y, por lo general, estas excepciones están ocultas en los constmétodos que funcionan con datos privados.

(Nota: me refiero a la visibilidad de datos y métodos varias veces. Estoy hablando de miembros marcados como públicos frente a privados o protegidos, que es un tipo de protección de objetos totalmente diferente que se analiza aquí ).

Dan L
fuente
8
Además, usar const_castpara modificar una parte de un constobjeto produce un comportamiento indefinido.
Brian
No estoy de acuerdo porque obliga al cliente API a destruir la protección constante de los objetos . Si estaba utilizando const_castpara implementar la mutación de las variables miembro en un constmétodo, que no le pediría el cliente al hacer el reparto - que lo haría dentro del método por el const_casting this. Básicamente, le permite evitar la constidad en miembros arbitrarios en un sitio de llamada específico , mientras mutableque le permite eliminar la const en un miembro específico en todos los sitios de llamada. Esto último suele ser lo que desea para el uso típico (almacenamiento en caché, estadísticas), pero a veces const_cast se ajusta al patrón.
BeeOnRope
1
El const_castpatrón se ajusta mejor en algunos casos, como cuando desea modificar temporalmente un miembro y luego restaurarlo (más o menos como boost::mutex). El método es lógicamente constante ya que el estado final es el mismo que el inicial, pero desea realizar ese cambio transitorio. const_castpuede ser útil allí porque le permite deshacerse de la constante específicamente en ese método donde se deshaga la mutación, pero mutableno sería tan apropiada ya que eliminaría la protección constante de todos los métodos, que no necesariamente siguen el "do , deshacer "patrón.
BeeOnRope
2
Sin embargo, la posible ubicación del objeto definido const en la memoria de solo lectura (más generalmente, la memoria marcada como de solo lectura) y el lenguaje estándar asociado que permite esto hace const_castuna posible bomba de tiempo. mutableno tiene ese problema ya que dichos objetos no se pueden colocar en la memoria de solo lectura.
BeeOnRope
75

Su uso con boost :: mutex es exactamente para lo que esta palabra clave está destinada. Otro uso es para el almacenamiento en caché interno de resultados para acelerar el acceso.

Básicamente, 'mutable' se aplica a cualquier atributo de clase que no afecte el estado externamente visible del objeto.

En el código de muestra en su pregunta, mutable puede ser inapropiado si el valor de done_ afecta el estado externo, depende de lo que esté en el ...; parte.

Frank Szczerba
fuente
35

Mutable es para marcar un atributo específico como modificable desde dentro de los constmétodos. Ese es su único propósito. Piensa cuidadosamente antes de usarlo, porque tu código probablemente será más limpio y más legible si cambias el diseño en lugar de usarlo mutable.

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

Entonces, si la locura anterior no es para lo que es mutable, ¿para qué sirve? Aquí está el caso sutil: mutable es para el caso donde un objeto es lógicamente constante, pero en la práctica necesita cambiar. Estos casos son pocos y distantes entre sí, pero existen.

Los ejemplos que da el autor incluyen el almacenamiento en caché y las variables de depuración temporales.

John Millikin
fuente
2
Creo que este enlace da el mejor ejemplo de un escenario en el que mutable es útil. Casi parece que se usan exclusivamente para la depuración. (por el uso correcto)
enthusiasticgeek
El uso de mutablepuede hacer que el código sea más legible y limpio. En el siguiente ejemplo, readpuede ser constcomo se esperaba. `mutable m_mutex; Contenedor m_container; void add (Item item) {Lockguard lock (m_mutex); m_container.pushback (artículo); } Elemento read () const {Lockguard lock (m_mutex); return m_container.first (); } `
Jue. Thielemann
Hay un caso de uso extremadamente popular: el recuento de referencias.
Seva Alekseyev
33

Es útil en situaciones en las que tiene estado interno oculto, como un caché. Por ejemplo:

clase HashTable
{
...
público:
    cadena de búsqueda (clave de cadena) const
    {
        if (clave == lastKey)
            return lastValue;

        valor de cadena = lookupInternal (clave);

        lastKey = clave;
        lastValue = value;

        valor de retorno;
    }

privado:
    cadena mutable lastKey, lastValue;
};

Y luego puede hacer que un const HashTableobjeto todavía use su lookup()método, que modifica la memoria caché interna.

Adam Rosenfield
fuente
9

mutable existe como se infiere para permitir que uno modifique datos en una función constante.

La intención es que pueda tener una función que "no haga nada" en el estado interno del objeto, por lo que debe marcar la función const, pero es posible que necesite modificar algunos de los objetos de manera que no afecten su correcto funcionalidad

La palabra clave puede actuar como una pista para el compilador: un compilador teórico podría colocar un objeto constante (como un global) en la memoria que se marcó como de solo lectura. La presencia de mutableindicios de que esto no debe hacerse.

Aquí hay algunas razones válidas para declarar y usar datos mutables:

  • Hilo de seguridad. Declarar a mutable boost::mutexes perfectamente razonable.
  • Estadísticas. Contando el número de llamadas a una función, dados algunos o todos sus argumentos.
  • Memorización Calcular una respuesta costosa y luego almacenarla para futuras referencias en lugar de volver a calcularla.
Lloyd
fuente
2
Buena respuesta, excepto el comentario sobre mutable como una "pista". Esto hace que parezca que el miembro mutable a veces no será mutable si el compilador coloca el objeto en la ROM. El comportamiento de mutable está bien definido.
Richard Corden
2
Además de colocar un objeto constante en la memoria de solo lectura, el compilador también puede decidir optimizar las llamadas continuas de un ciclo, por ejemplo. Un contador de estadísticas mutable en una función const de otro modo todavía permitirá dicha optimización (y contará solo una llamada) en lugar de evitar la optimización solo por contar más llamadas.
Hagen von Eitzen
@HagenvonEitzen: estoy bastante seguro de que es incorrecto. Un compilador no puede levantar funciones de un bucle a menos que pueda probar que no hay efectos secundarios. Esa prueba generalmente implica inspeccionar realmente la implementación de la función (a menudo después de que está en línea) y no confiar en ella const(y dicha inspección tendrá éxito o fallará independientemente de consto mutable). Simplemente declarar la función constno es suficiente: una constfunción es libre de tener efectos secundarios como modificar una variable global o algo que se pasa a la función, por lo que no es una garantía útil para esa prueba.
BeeOnRope
Ahora, algunos compiladores tienen extensiones especiales como _attribute __ ((const)) y __attribute __ ((puro)) de gcc, que _tienen tales efectos , pero eso solo está relacionado tangencialmente con la constpalabra clave en C ++.
BeeOnRope
8

Bueno, sí, eso es lo que hace. Lo uso para miembros que son modificados por métodos que no cambian lógicamente el estado de una clase, por ejemplo, para acelerar las búsquedas mediante la implementación de un caché:

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;
};

Ahora, debe usar esto con cuidado: los problemas de concurrencia son una gran preocupación, ya que una persona que llama podría asumir que son seguros para subprocesos si solo usa constmétodos. Y, por supuesto, la modificación de mutabledatos no debería cambiar el comportamiento del objeto de manera significativa, algo que podría ser violado por el ejemplo que di si, por ejemplo, se espera que los cambios escritos en el disco sean visibles de inmediato para la aplicación .

Shog9
fuente
6

Mutable se usa cuando tiene una variable dentro de la clase que solo se usa dentro de esa clase para señalar cosas como, por ejemplo, un mutex o un candado. Esta variable no cambia el comportamiento de la clase, pero es necesaria para implementar la seguridad de subprocesos de la clase misma. Por lo tanto, si sin "mutable", no podría tener funciones "const" porque esta variable deberá cambiarse en todas las funciones que están disponibles para el mundo exterior. Por lo tanto, mutable se introdujo para hacer que una variable miembro se pueda escribir incluso mediante una función const.

El mutable especificado informa tanto al compilador como al lector que es seguro y se espera que una variable miembro pueda modificarse dentro de una función miembro const.

mkschreder
fuente
4

mutable se usa principalmente en un detalle de implementación de la clase. El usuario de la clase no necesita saberlo, por lo tanto, el método cree que "debería" ser constante. Su ejemplo de que un mutex sea mutable es un buen ejemplo canónico.

Greg Rogers
fuente
4

Su uso no es un truco, aunque, como muchas cosas en C ++, mutable puede ser pirateado para un programador perezoso que no quiere regresar y marcar algo que no debería ser constante como no constante.

JohnMcG
fuente
3

Use "mutable" cuando para cosas que son LÓGICAMENTE sin estado para el usuario (y por lo tanto deberían tener captadores "constantes" en las API de la clase pública) pero NO son apátridas en la IMPLEMENTACIÓN subyacente (el código en su .cpp).

Los casos que lo uso con más frecuencia son la inicialización diferida de miembros sin estado de "datos antiguos". Es decir, es ideal en los casos estrechos cuando tales miembros son caros de construir (procesador) o llevar (memoria) y muchos usuarios del objeto nunca los pedirán. En esa situación, desea una construcción perezosa en el back-end para el rendimiento, ya que el 90% de los objetos construidos nunca necesitarán construirlos, pero aún debe presentar la API sin estado correcta para el consumo público.

Zack Yezek
fuente
2

Mutable cambia el significado de constconst bit a const lógico para la clase.

Esto significa que las clases con miembros mutables ya no serán constantes y no aparecerán en las secciones de solo lectura del ejecutable.

Además, modifica la verificación de tipos al permitir que las constfunciones miembro cambien miembros mutables sin usar 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.
}

Consulte las otras respuestas para obtener más detalles, pero quería resaltar que no se trata simplemente de seguridad de tipos y que afecta el resultado compilado.

Kevin Cox
fuente
1

En algunos casos (como iteradores mal diseñados), la clase necesita mantener un conteo u otro valor incidental, que realmente no afecta el "estado" principal de la clase. Esto es más a menudo donde veo mutable utilizado. Sin mutable, se vería obligado a sacrificar toda la coherencia de su diseño.

También me parece un truco la mayor parte del tiempo. Útil en muy, muy pocas situaciones.

Joe Schneider
fuente
1

El ejemplo clásico (como se menciona en otras respuestas) y la única situación en la que he visto la mutablepalabra clave utilizada hasta ahora, es para almacenar en caché el resultado de un proceso complicado.Get método , donde el caché se implementa como un miembro de datos de la clase y no como un variable estática en el método (por razones de compartir entre varias funciones o limpieza simple).

En general, las alternativas al uso de la mutablepalabra clave suelen ser una variable estática en el método o el const_casttruco.

Otra explicación detallada está aquí .

Daniel Hershcovich
fuente
1
Nunca he oído hablar del uso de miembros estáticos como una alternativa general a los miembros mutables. Y const_castes solo para cuando sepa (o haya sido garantizado) que algo no cambiará (por ejemplo, cuando interfiere con las bibliotecas C) o cuando sabe que no se declaró const. Es decir, la modificación de una variable constante const genera resultados indefinidos.
Sebastian Mach
1
@phresnel Por "variables estáticas" me refería a variables automáticas estáticas en el método (que permanecen entre llamadas). Y const_castse puede usar para modificar un miembro de la clase en un constmétodo, que es a lo que me referí ...
Daniel Hershcovich
1
Eso no estaba claro para mí, ya que escribiste "en general" :) Con respecto a la modificación const_cast, como se dijo, esto solo se permite cuando el objeto no se declaró const. Por ejemplo const Frob f; f.something();, con void something() const { const_cast<int&>(m_foo) = 2;resultados en comportamiento indefinido.
Sebastian Mach
1

El mutable puede ser útil cuando está anulando una función virtual constante y desea modificar su variable miembro de clase secundaria en esa función. En la mayoría de los casos, no querrá alterar la interfaz de la clase base, por lo que debe usar su propia variable de miembro mutable.

Saurabh
fuente
1

La palabra clave mutable es muy útil al crear apéndices para fines de prueba de clase. Puede desactivar una función constante y aún puede aumentar los contadores (mutables) o cualquier funcionalidad de prueba que haya agregado a su código auxiliar. Esto mantiene intacta la interfaz de la clase stubbed.

Martin G
fuente
0

Uno de los mejores ejemplos donde usamos mutable es, en copia profunda. En el constructor de copia enviamos const &objcomo argumento. Por lo tanto, el nuevo objeto creado será de tipo constante. Si queremos cambiar (en su mayoría no cambiaremos, en casos excepcionales podemos cambiar) los miembros en este objeto constante creado recientemente, debemos declararlo como mutable.

mutableLa clase de almacenamiento solo se puede usar en un miembro de datos no estático y no constante de una clase. El miembro de datos mutables de una clase se puede modificar incluso si es parte de un objeto que se declara como constante.

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

En el ejemplo anterior, podemos cambiar el valor de la variable miembro xaunque sea parte de un objeto que se declara como constante. Esto se debe a que la variable xse declara como mutable. Pero si intenta modificar el valor de la variable miembro y, el compilador arrojará un error.

Venkatakrishna Kalepalli
fuente
-1

La palabra clave 'mutable' es en realidad una palabra clave reservada. A menudo se usa para variar el valor de la variable constante. Si desea tener múltiples valores de una constante, use la palabra clave mutable.

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