¿Tiene la palabra clave 'mutable' algún otro propósito además de permitir que una función constante modifique la variable?

StackOverflow https://stackoverflow.com/questions/105014

  •  01-07-2019
  •  | 
  •  

Pregunta

Hace un tiempo me encontré con un código que marcaba una variable miembro de una clase con el mutable palabra clave.Por lo que puedo ver, simplemente te permite modificar una variable en un const mé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 utilizado esta técnica en una clase, marcando un boost::mutex como mutable permitiendo const funciona para bloquearlo por razones de seguridad de subprocesos, pero, para ser honesto, parece un truco.

¿Fue útil?

Solución

Permite la diferenciación de constante bit a bit y constante lógica.La constante lógica es cuando un objeto no cambia de una manera que sea visible a través de la interfaz pública, como en el 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.

Desde c++11 mutable se puede usar en una lambda para indicar que las cosas capturadas por valor son modificables (no lo son de forma predeterminada):

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

Otros consejos

El mutable palabra clave es una manera de perforar el const velo que cubres tus objetos.Si tiene una referencia constante o un puntero a un objeto, no puede modificar ese objeto de ninguna manera. excepto cuando y como se marca mutable.

Con tu const referencia o puntero al que está obligado:

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

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

Internamente aquellos const Los métodos que son visibles para usted también pueden escribir en miembros de datos que están marcados mutable.Básicamente, el velo constante se perfora por completo.Depende completamente del diseñador de API garantizar que mutable no destruye el const concepto y sólo se utiliza en casos especiales útiles.El mutable La palabra clave ayuda porque marca claramente los miembros de datos que están sujetos a estos casos especiales.

En la práctica puedes utilizar const obsesivamente en todo su código base (esencialmente desea "infectar" su código base con el const "enfermedad").En este mundo los consejos y referencias son const Con muy pocas excepciones, genera un código que es más fácil de razonar y comprender.Para una digresión interesante, consulte "transparencia referencial".

Sin el mutable palabra clave que eventualmente te verás obligado a usar const_cast para manejar los diversos casos especiales útiles que permite (almacenamiento en caché, recuento de referencias, datos de depuración, etc.).Desafortunadamente const_cast es significativamente más destructivo que mutable porque obliga a la API cliente para destruir el const protección de los objetos que está utilizando.Además provoca daños generalizados const destrucción: const_castLa utilización de un puntero o referencia constante permite acceso sin restricciones a la escritura y a las llamadas a métodos de los miembros visibles.A diferencia de mutable requiere que el diseñador de API ejerza un control detallado sobre la const excepciones, y normalmente estas excepciones están ocultas en const métodos que operan con datos privados.

(NÓTESE BIEN.Me refiero a datos y método. visibilidad unas pocas veces.Me refiero a miembros marcados como públicos vs.privado o protegido, que es un tipo totalmente diferente de protección de objetos discutido aquí.)

Su uso con boost::mutex es exactamente para lo que está destinada esta palabra clave.Otro uso es 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 visible externamente del objeto.

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

Mutable sirve para marcar un atributo específico como modificable desde dentro const métodos.Ese es su único propósito.Piénselo detenidamente antes de usarlo, porque su código probablemente será más limpio y legible si cambia 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é es?Aquí está el caso sutil:Mutable es para el caso donde un objeto es lógicamente constante, pero en la práctica debe cambiar.Estos casos son pocos y distantes, pero existen.

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

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

class HashTable
{
...
public:
    string lookup(string key) const
    {
        if(key == lastKey)
            return lastValue;

        string value = lookupInternal(key);

        lastKey = key;
        lastValue = value;

        return value;
    }

private:
    mutable string lastKey, lastValue;
};

Y luego puedes tener un const HashTable El objeto todavía usa su lookup() método, que modifica el caché interno.

Bueno, sí, eso es lo que hace.Lo uso para miembros que son modificados por métodos que no lógicamente cambiar el estado de una clase, por ejemplo, para acelerar las búsquedas implementando 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 usan const métodos.Y por supuesto, modificando mutable los datos no deberían cambiar el comportamiento del objeto de manera significativa, algo que podría violarse en el ejemplo que di si, por ejemplo, se esperara que los cambios escritos en el disco fueran inmediatamente visibles para la aplicación.

mutable existe como usted infiere para permitir que uno modifique datos en una función que de otro modo sería constante.

La intención es que pueda tener una función que "no haga nada" en el estado interno del objeto, por lo que marca la función const, pero es posible que realmente necesites modificar algunos de los estados de los objetos de manera que no afecten su funcionalidad correcta.

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 marcada como de solo lectura.La presencia de mutable insinúa que esto no debería hacerse.

A continuación se presentan algunas razones válidas para declarar y utilizar datos mutables:

  • Seguridad del hilo.Declarar un mutable boost::mutex es perfectamente razonable.
  • Estadísticas.Contar 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 referencia futura en lugar de volver a calcularla.

Mutable se usa cuando tienes 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 bloqueo.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 no tuviera "mutable", no podría tener funciones "constantes" porque esta variable deberá cambiarse en todas las funciones que estén disponibles para el mundo exterior.Por lo tanto, se introdujo mutable para hacer que una variable miembro pueda escribirse incluso mediante una función constante.

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

mutable se utiliza principalmente en un detalle de implementación de la clase.El usuario de la clase no necesita saberlo, por lo tanto, los métodos que cree que "deberían" ser constantes pueden serlo.Su ejemplo de que un mutex sea mutable es un buen ejemplo canónico.

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

Utilice "mutable" cuando se trate de cosas que LÓGICAMENTE no tienen 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 en los que lo uso con más frecuencia son la inicialización diferida de miembros de "datos antiguos" sin estado.Es decir, es ideal en casos concretos en los que dichos miembros son costosos de construir (procesador) o transportar (memoria) y muchos usuarios del objeto nunca los solicitarán.En esa situación, desea una construcción diferida en el back-end para mejorar el rendimiento, ya que el 90% de los objetos creados nunca necesitarán compilarlos en absoluto, pero aún así debe presentar la API sin estado correcta para el consumo público.

Mutable cambia el significado de const de constante bit a bit a constante lógica para la clase.

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

Además, modifica la verificación de tipos al permitir const funciones miembro para cambiar 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 es solo para seguridad de tipos y que afecta el resultado compilado.

En algunos casos (como iteradores mal diseñados), la clase necesita mantener un recuento o algún otro valor incidental, que realmente no afecta el "estado" principal de la clase.Aquí es donde veo con mayor frecuencia el uso de mutables.Sin mutable, se vería obligado a sacrificar toda la consistencia de su diseño.

A mí también me parece un truco la mayor parte del tiempo.Útil en muy muy pocas situaciones.

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

En general, las alternativas al uso del mutable La palabra clave suele ser una variable estática en el método o en el const_cast truco.

Otra explicación detallada está en aquí.

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

La palabra clave mutable es muy útil al crear resguardos para fines de prueba de clase.Puede utilizar una función constante y aún así poder 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 stubped.

Uno de los mejores ejemplos en los que usamos mutable es en copia profunda.en el constructor de copias enviamos const &obj como argumento.Entonces el nuevo objeto creado será de tipo constante.Si queremos cambiar (la mayoría de las veces no cambiaremos, en casos excepcionales podemos cambiar) los miembros en este objeto constante recién creado, debemos declararlo como mutable.

mutable La clase de almacenamiento solo se puede usar en miembros de datos no estáticos y no constantes de una clase.El miembro de datos mutable de una clase se puede modificar incluso si es parte de un objeto declarado 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. x aunque es parte de un objeto que se declara como constante.Esto se debe a que la variable x se declara mutable.Pero si intentas modificar el valor de la variable miembro y, el compilador arrojará un error.

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

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top