¿La palabra clave ‘mutable’ tiene algún otro propósito que no sea permitir que una función de miembro const modifique un miembro de datos?

11 minutos de lectura

Avatar de usuario de Rob
Robar

Hace un tiempo, encontré un código que marcaba un miembro de datos de una clase con el mutable palabra clave. Por lo que puedo ver, simplemente le permite modificar un miembro en un const-método de miembro calificado:

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 un boost::mutex como mutablepermitiendo const funciona para bloquearlo por razones de seguridad de subprocesos, pero, para ser honesto, se siente como un truco.

  • Sin embargo, una pregunta, si no está modificando nada, ¿por qué necesita usar un mutex en primer lugar? Solo quiero entender esto.

    – Misgevolución

    19 de agosto de 2016 a las 4:39


  • @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 si solo doy identificadores no constantes a los amigos, los enemigos obtienen un identificador constante. Los amigos pueden modificar, los enemigos no.

    – iheanyi

    22 mayo 2017 a las 21:35


  • Nota: aquí hay un gran ejemplo del uso de la palabra clave mutable: stackoverflow.com/questions/15999123/…

    – Gabriel grapas

    5 de agosto de 2017 a las 17:19

  • Desearía que pudiera usarse para anular const (de tipos) para que no tenga que hacer esto: class A_mutable{}; using A = A_mutable const; mutable_t<A> a;si quiero const-by-default, es decir mutable A a; (mutable explícito) y A a; (const implícita).

    – alfC

    18 oct 2019 a las 22:01


  • @Misgevolution porque otros subprocesos se están modificando.

    – Dexter

    3 de febrero de 2022 a las 9:48

Avatar de usuario de KeithB
keithb

Permite la diferenciación de const bit a bit y const lógico. 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 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.

Desde c++11 mutable se puede usar en una lambda para indicar que las cosas capturadas por valor son modificables (no lo son de manera 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

  • ‘mutable’ no afecta en absoluto la constancia bit a bit/lógica. C++ es solo bitwise const y la palabra clave ‘mutable’ se pueden usar para excluir miembros de esta verificación. No es posible lograr una constante ‘lógica’ en C++ que no sea a través de abstracciones (por ejemplo, SmartPtrs).

    -Richard Corden

    22 de septiembre de 2008 a las 8:13

  • @Richard: te estás perdiendo el punto. No hay una palabra clave de “const lógico”, es cierto, más bien, es una diferenciación conceptual que hace el programador para decidir qué miembros deben excluirse haciéndolos mutables, en función de una comprensión de lo que constituye el estado lógico observable del objeto.

    – Tony Delroy

    29 de julio de 2011 a las 2:11

  • @ajay Sí, ese es el objetivo de marcar una variable miembro como mutable, para permitir que se cambie en objetos const.

    – KeithB

    6 de marzo de 2013 a las 14:48

  • ¿Por qué uno necesita mutable en lambdas? ¿No sería suficiente capturar la variable por referencia?

    – Jorge

    24 de abril de 2013 a las 11:02

  • @Giorgio: La diferencia es que el modificado x dentro de la lambda permanece dentro de la lambda, es decir, la función lambda solo puede modificar su propia copia de x. El cambio no se ve afuera, el original x sigue sin cambios. Considere que las lambdas se implementan como clases de funtores; las variables capturadas corresponden a las variables miembro.

    – Sebastián Mach

    5 de junio de 2013 a las 15:28

Avatar de usuario de Dan L
dan l

El mutable palabra clave es una forma 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á restringido:

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

El mutable 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 esos const los métodos que son visibles para usted también pueden escribir en los miembros de datos que están marcados mutable. Esencialmente, el velo constante se perfora de manera integral. Depende completamente del diseñador de la API asegurarse de 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 se puede utilizar const obsesivamente a través de su base de código (esencialmente quiere “infectar” su base de código con el const “enfermedad”). En este mundo los punteros y las referencias son const con muy pocas excepciones, produciendo un código que es más fácil de razonar y comprender. Para una digresión interesante, busque “transparencia referencial”.

Sin el mutable palabra clave que eventualmente se verá obligado a usar const_cast para manejar los diversos casos especiales útiles que permite (almacenamiento en caché, conteo de referencias, datos de depuración, etc.). Desafortunadamente const_cast es significativamente más destructivo que mutable porque fuerza la API cliente para destruir el const protección de los objetos que está utilizando. Además, causa generalizado const destrucción: const_castEl uso de un puntero constante o una referencia permite el acceso sin restricciones de escritura y llamada de método a los miembros visibles. A diferencia de mutable requiere que el diseñador de API ejerza un control detallado sobre el const excepciones, y por lo general estas excepciones están ocultas en const métodos que operan sobre datos privados.

(Nota: me refiero a datos y método visibilidad unas pocas 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í).

  • Además, usando const_cast para modificar una parte de un const objeto produce un comportamiento indefinido.

    – Brian Bi

    6 de abril de 2015 a las 0:25

  • no estoy de acuerdo con porque obliga al cliente API a destruir la protección constante de los objetos. si estuvieras usando const_cast para implementar la mutación de las variables miembro en un const método, no le pedirías al cliente que haga el yeso, lo harías dentro del método por const_castEn g this. Básicamente, le permite omitir la constancia en miembros arbitrarios en un sitio de llamada específicomientras mutable vamos a eliminar const en un miembro específico en todos los sitios de llamada. Este último suele ser lo que desea para el uso típico (almacenamiento en caché, estadísticas), pero a veces el const_cast se ajusta al patrón.

    – BeeOnRope

    18 de junio de 2017 a las 22:39


  • El const_cast El patrón encaja mejor en algunos casos, como cuando desea modificar temporalmente un miembro y luego restaurarlo (prácticamente 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_cast puede ser útil allí porque te permite descartar const específicamente en ese método donde se deshará la mutación, pero mutable no sería tan apropiado ya que eliminaría la protección constante de todo métodos, que no necesariamente siguen el patrón “hacer, deshacer”.

    – BeeOnRope

    18 de junio de 2017 a las 22:48

  • La posible colocación de const definido objeto en la memoria de sólo lectura (más generalmente, la memoria marcado solo lectura) y el lenguaje estándar asociado que permite esto hace const_cast aunque una posible bomba de relojería. mutable no tiene tal problema ya que dichos objetos no se pueden colocar en la memoria de solo lectura.

    – BeeOnRope

    18 de junio de 2017 a las 22:54

Su uso con boost::mutex es exactamente para lo que está destinada esta palabra clave. Otro uso es para el almacenamiento en caché de resultados internos 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 en su pregunta, mutable podría ser inapropiado si el valor de done_ afecta el estado externo, depende de lo que esté en …; parte.

Mutable es para marcar un atributo específico como modificable desde dentro const métodos. Ese es su único propósito. Piense cuidadosamente antes de usarlo, porque su código probablemente será más limpio y más legible si cambia el diseño en lugar de usar 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 en que 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.

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.

  • El ejemplo es bueno, pero esta práctica esconde una consecuencia peligrosa. Alguien que busque la llamada de búsqueda puede pensar que es seguro para subprocesos ya que “no” cambia el estado del objeto debido al calificador const. Más tarde, cuando las cosas no funcionan… se desperdician horas de trabajo para encontrar la condición de carrera. Es una práctica terrible.

    – Adriel Jr.

    9 dic 2021 a las 19:40


Avatar de usuario de Lloyd
lloyd

mutable existe como usted infiere para permitir que uno modifique los 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 marca la función constpero es posible que realmente necesite 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 que estaba marcada como de solo lectura. La presencia de mutable insinúa que esto no debe hacerse.

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

  • Seguridad del hilo. Declarando 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.
  • Memoización. Calcular una respuesta costosa y luego almacenarla para referencia futura en lugar de volver a calcularla.

  • El ejemplo es bueno, pero esta práctica esconde una consecuencia peligrosa. Alguien que busque la llamada de búsqueda puede pensar que es seguro para subprocesos ya que “no” cambia el estado del objeto debido al calificador const. Más tarde, cuando las cosas no funcionan… se desperdician horas de trabajo para encontrar la condición de carrera. Es una práctica terrible.

    – Adriel Jr.

    9 dic 2021 a las 19:40


Avatar de usuario de Shog9
shog9

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 puede 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 ser violado por el ejemplo que di si, por ejemplo, se esperaba que los cambios escritos en el disco fueran inmediatamente visibles para la aplicación.

¿Ha sido útil esta solución?