Funciones miembro const
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 29 de 104.
Has visto la palabra clave const en los métodos getter en la lección anterior. Pero, ¿qué significa exactamente cuando const aparece después de la lista de parámetros de una función? Una función miembro const promete no modificar ninguna variable miembro del objeto.
class Rectangle {
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() const { // función miembro constante
return width * height; // Se permite la lectura de miembros
}
void setWidth(int w) { // No const - modifica el objeto
width = w;
}
};El const después de la lista de parámetros le dice al compilador: "Esta función no cambiará el estado del objeto." Si intentas modificar una variable miembro dentro de una función const, el compilador producirá un error.
Esto se vuelve esencial al trabajar con objetos const o referencias const. Un objeto const solo puede llamar a funciones miembro const:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK - getArea() es const
// rect.setWidth(10); // ERROR - setWidth() no es const
}Marcar las funciones como const cuando no modifican el objeto es una buena práctica. Documenta tu intención, permite que la función trabaje con objetos const y ayuda al compilador a detectar modificaciones accidentales. Cualquier función miembro que solo lea datos debería marcarse como const.
Desafío
FácilConstruyamos un convertidor de temperatura que demuestre cuándo y por qué usar funciones miembro const. Crearás una clase donde algunos métodos solo leen datos (y deben ser const) mientras que otros modifican el estado del objeto.
Crearás dos archivos para organizar tu código:
Temperature.h: Define una claseTemperatureque almacena un valor de temperatura y proporciona varias formas de leerlo y modificarlo. Tu clase debe tener:- Un miembro privado
celsius(double) para almacenar la temperatura - Un constructor que toma un valor inicial en Celsius
- Un método
getCelsius()que devuelve el valor almacenado — este debe ser const ya que solo lee datos - Un método
getFahrenheit()que calcula y devuelve la temperatura en Fahrenheit usando la fórmulacelsius * 9.0 / 5.0 + 32.0— también const ya que no modifica nada - Un método
getKelvin()que devuelve la temperatura en Kelvin usandocelsius + 273.15— const también - Un método
setCelsius(double value)que actualiza la temperatura almacenada — este no puede ser const ya que modifica el objeto - Un método
adjustBy(double delta)que suma el delta a la temperatura actual — también non-const
- Un miembro privado
main.cpp: Demuestra cómo funcionan las funciones miembro const tanto con objetos regulares como con objetos const. Lee un valor de temperatura inicial de la entrada, luego:- Crea un objeto
Temperaturecon el valor de entrada - Imprime
"Initial: <celsius>C = <fahrenheit>F = <kelvin>K" - Ajusta la temperatura por
10.0grados - Imprime
"After adjustment: <celsius>C" - Crea una función auxiliar
void printReadings(const Temperature& temp)que toma una referencia const e imprime"Reading: <celsius>C, <fahrenheit>F"— esta función solo puede llamar a métodos const en temp - Llama a
printReadings()con tu objeto de temperatura - Establece la temperatura a
0.0(punto de congelación) - Imprime
"Freezing point: <celsius>C = <fahrenheit>F"
- Crea un objeto
Formatea todos los valores de temperatura con un decimal usando std::fixed y std::setprecision(1) de <iomanip>.
La idea clave aquí es que printReadings() recibe una referencia const, por lo que solo puede llamar a métodos marcados como const. Es por esto que marcar correctamente tus métodos getter como const es importante — permite que funcionen en contextos donde el objeto no puede ser modificado.
Hoja de referencia
Una función miembro const promete no modificar ninguna variable miembro del objeto. La palabra clave const se coloca después de la lista de parámetros de la función:
class Rectangle {
int width;
int height;
public:
int getArea() const { // función miembro const
return width * height; // Se permite leer miembros
}
void setWidth(int w) { // No-const - modifica el objeto
width = w;
}
};Las funciones miembro const solo pueden leer variables miembro, no modificarlas. Si intentas modificar una variable miembro dentro de una función const, el compilador producirá un error.
Un objeto const o una referencia const solo puede llamar a funciones miembro const:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK - getArea() es const
// rect.setWidth(10); // ERROR - setWidth() no es const
}Marca las funciones como const cuando no modifiquen el objeto. Esto documenta tu intención, permite que la función trabaje con objetos const y ayuda al compilador a detectar modificaciones accidentales. Cualquier función miembro que solo lea datos debe marcarse como const.
Pruébalo tú mismo
#include <iostream>
#include <iomanip>
#include "Temperature.h"
using namespace std;
// TODO: Implementar los métodos de la clase Temperature aquí
// Constructor
Temperature::Temperature(double initialCelsius) {
// TODO: Inicializar celsius
}
// TODO: Implementar getCelsius() como const
// TODO: Implementar getFahrenheit() como const
// TODO: Implementar getKelvin() como const
// TODO: Implementar setCelsius(double value)
// TODO: Implementar adjustBy(double delta)
// TODO: Crear una función auxiliar printReadings que reciba un const Temperature&
// y que imprima "Reading: <celsius>C, <fahrenheit>F"
// Note: ¡Esta función solo puede llamar a métodos const en temp!
int main() {
double initialTemp;
cin >> initialTemp;
// Configurar el formato de salida
cout << fixed << setprecision(1);
// TODO: Crear un objeto Temperature con el valor de entrada
// TODO: Imprimir "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"
// TODO: Ajustar la temperatura en 10.0 grados
// TODO: Imprimir "After adjustment: <celsius>C"
// TODO: Llamar a printReadings() con tu objeto de temperatura
// TODO: Establecer la temperatura a 0.0 (punto de congelación)
// TODO: Imprimir "Freezing point: <celsius>C = <fahrenheit>F"
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