Palabra clave mutable
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 30 de 104.
Has aprendido que las funciones miembro const no pueden modificar ninguna variable miembro. Pero a veces necesitas cambiar un miembro que no afecta al estado lógico del objeto, como una caché o un contador de acceso. La palabra clave mutable proporciona una excepción a la restricción const.
Un miembro mutable puede ser modificado incluso dentro de una función miembro const:
class DataFetcher {
std::string data;
mutable int accessCount; // Puede ser modificado en funciones const
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Permitido porque accessCount es mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Sin mutable, incrementar accessCount dentro de la función const getData() causaría un error de compilación. La palabra clave mutable le dice al compilador: "Se permite que este miembro cambie incluso cuando el objeto se considera const."
Los casos de uso comunes para mutable incluyen el almacenamiento en caché de valores calculados, el seguimiento de estadísticas de acceso y primitivas de sincronización como mutexes. En cada caso, la modificación no cambia lo que el objeto representa lógicamente: un valor almacenado en caché es solo una optimización y un contador de acceso son metadatos sobre el uso.
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};Use mutable con moderación; solo debe aplicarse a miembros que no afecten al estado observable del objeto.
Desafío
FácilVamos a construir un visor de documentos que rastrea cuántas veces se ha visto un documento, sin romper la corrección de const (const-correctness) de nuestros métodos de solo lectura. Este es un caso de uso perfecto para la palabra clave mutable.
Crearás dos archivos para organizar tu código:
Document.h: Define una claseDocumentque almacena el contenido y rastrea secretamente las estadísticas de visualización. Tu clase necesita:- Miembros privados:
title(string),content(string), y unmutable int viewCountque pueda ser modificado incluso en métodos const - Un constructor que reciba un título y contenido, inicializando
viewCounten0 - Un método const
getTitle()que devuelva el título - Un método const
getContent()que incrementeviewCount(permitido porque es mutable) y devuelva el contenido - Un método const
getViewCount()que devuelva cuántas veces se ha accedido al contenido - Un método const
getPreview()que devuelva los primeros 20 caracteres del contenido seguidos de"..."— esto NO debe incrementar el contador de visitas ya que es solo una vista previa
- Miembros privados:
main.cpp: Demuestra cómo mutable permite rastrear estadísticas de acceso manteniendo los métodos lógicamente const. Lee el título y el contenido de un documento desde la entrada (cada uno en una línea separada), luego:- Crea un
Documentcon los valores de entrada - Imprime
"Title: <title>" - Imprime
"Preview: <preview>" - Imprime
"Views after preview: <count>" - Accede al contenido completo usando
getContent()e imprime"Content: <content>" - Imprime
"Views after first read: <count>" - Accede al contenido de nuevo e imprime
"Content: <content>" - Imprime
"Views after second read: <count>" - Crea una función auxiliar
void displayDocument(const Document& doc)que imprima"[Const Access] <title>: <content>"— esto demuestra que nuestros métodos const funcionan con referencias const - Llama a
displayDocument()con tu documento - Imprime
"Final view count: <count>"
- Crea un
El contador de visitas debe ser 0 después de la vista previa (ya que las vistas previas no cuentan como visitas completas), luego incrementarse cada vez que se llame a getContent(). La palabra clave mutable hace esto posible — sin ella, no podrías modificar viewCount dentro de métodos const como getContent().
Si el contenido tiene menos de 20 caracteres, getPreview() debe devolver todo el contenido seguido de "...".
Hoja de referencia
La palabra clave mutable permite que una variable miembro sea modificada incluso dentro de funciones miembro const:
class DataFetcher {
std::string data;
mutable int accessCount; // Can be modified in const functions
public:
DataFetcher(std::string d) : data(d), accessCount(0) {}
std::string getData() const {
accessCount++; // Allowed because accessCount is mutable
return data;
}
int getAccessCount() const {
return accessCount;
}
};Sin mutable, modificar una variable miembro dentro de una función const causaría un error de compilación. La palabra clave le indica al compilador que este miembro tiene permitido cambiar incluso cuando el objeto se considera const.
Los casos de uso comunes incluyen el almacenamiento en caché de valores calculados, el seguimiento de estadísticas de acceso y las primitivas de sincronización. La modificación no debe cambiar lo que el objeto representa lógicamente:
class Circle {
double radius;
mutable double cachedArea;
mutable bool areaCached;
public:
double getArea() const {
if (!areaCached) {
cachedArea = 3.14159 * radius * radius;
areaCached = true;
}
return cachedArea;
}
};Use mutable con moderación, solo para miembros que no afecten el estado observable del objeto.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Document.h"
using namespace std;
// TODO: Implementar la función displayDocument
// Debe imprimir: "[Const Access] <title>: <content>"
void displayDocument(const Document& doc) {
// TODO: Implementar esta función
}
int main() {
// Leer la entrada
string title;
string content;
getline(cin, title);
getline(cin, content);
// TODO: Crear un Document con los valores de entrada
// TODO: Imprimir "Title: <title>"
// TODO: Imprimir "Preview: <preview>"
// TODO: Imprimir "Views after preview: <count>"
// TODO: Acceder al contenido completo e imprimir "Content: <content>"
// TODO: Imprimir "Views after first read: <count>"
// TODO: Acceder al contenido de nuevo e imprimir "Content: <content>"
// TODO: Imprimir "Views after second read: <count>"
// TODO: Llamar a displayDocument() con tu documento
// TODO: Imprimir "Final view count: <count>"
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico