Quién puede tocar tus datos
Cuando escribiste tu primera clase probablemente expusiste todos los miembros al mundo exterior. Eso funciona, pero desperdicia una de las principales razones por las que existen las clases: el encapsulamiento, es decir, ocultar el estado interno de una clase para que el resto del programa solo pueda interactuar con ella a través de una superficie controlada. Los especificadores de acceso son la forma de trazar esa frontera.
Hay exactamente tres: public, private y protected. Cada uno etiqueta los miembros que lo siguen, y la etiqueta decide qué código puede leerlos o escribirlos. Hazlo bien y tu clase impondrá sus propias reglas; hazlo mal y cualquier error en cualquier parte podrá corromper el estado de tu objeto.
Los tres especificadores
Un especificador es una palabra clave seguida de dos puntos. Todo miembro declarado después de él, hasta el siguiente especificador, queda bajo ese nivel de acceso.
Descomenta la última línea y el compilador se niega a construir: balance es private, así que main no puede manipularlo directamente. Ese es el objetivo: la única forma de cambiar el saldo es a través de deposit, lo que significa que más adelante puedes añadir validación (sin depósitos negativos, registro, límites) en un solo sitio y confiar en que siempre se aplica.
Aquí tienes el desglose completo:
// accesible desde...
// public cualquier sitio (cualquier código que tenga el objeto)
// private solo los miembros de la propia clase (+ friends)
// protected los miembros de la propia clase Y las clases derivadas (+ friends)
class frente a struct: el valor por defecto
Puedes escribir tantos bloques de especificadores como quieras, en cualquier orden. Lo que obtienen los miembros antes de que escribas el primero depende de si usaste class o struct:
- En una
class, los miembros sonprivatepor defecto. - En un
struct, los miembros sonpublicpor defecto.
Ese valor por defecto es la única diferencia a nivel de lenguaje entre las dos palabras clave. Un struct puede tener métodos, constructores y secciones private igual que una class.
La convención es usar struct para simples paquetes de datos públicos y class cuando quieres comportamiento y estado oculto, pero el compilador no lo impone; solo difiere el valor por defecto.
Encapsulamiento con getters y setters
El patrón cotidiano es: los datos van en una sección private y un método public da acceso controlado. Un getter de solo lectura devuelve el valor; un setter valida antes de asignar. Aquí es donde private da sus frutos.
Como celsius es private, no hay forma de colar un valor inválido: toda escritura debe pasar por setCelsius, que protege la invariante. Fíjate en que los getters están marcados como const: prometen no modificar el objeto, así que también puedes llamarlos sobre objetos const Temperature.
protected y la herencia
protected solo importa cuando entra en juego la herencia. Se comporta como private para el código externo, pero una clase derivada sí puede acceder a él. Úsalo para miembros que una subclase necesita legítimamente pero que el público aún no debería tocar.
Un error frecuente de principiante es recurrir a protected en cada miembro de datos "por si acaso una subclase lo necesita". Eso amplía silenciosamente el contrato de tu clase: ahora cada subclase puede depender de ese campo y no podrás cambiarlo con libertad. Prefiere private y promociona a protected solo cuando una clase derivada realmente necesite el acceso.
La válvula de escape friend
A veces una sola función o clase externa necesita legítimamente ver tus interioridades; un caso clásico es un operador como << que no puedes hacer miembro. La palabra clave friend concede a esa única entidad nombrada acceso a tus miembros private y protected, y a nada más.
friend es una excepción deliberada y quirúrgica: la propia clase nombra exactamente en quién confía, de modo que nadie puede concederse acceso desde fuera. Úsalo con moderación; si te encuentras añadiendo muchos friends, probablemente tus miembros no debían haber sido private en primer lugar, o tu diseño necesita replantearse.
Errores comunes que evitar
- Hacer públicos todos los miembros. Parece fácil, pero pierdes toda la validación y las invariantes que el encapsulamiento te ofrece. Por defecto, datos
privatecon métodospublic. - Olvidar el valor por defecto de
class.class Foo { int x; };hace quexsea private, así quefoo.x = 5no compilará. Si querías un simple paquete de datos, usastructo añade una etiquetapublic:. - Abusar de
protected. Es una frontera más débil queprivatey solo es relevante con la herencia. Recurrir a ella en todas partes acopla las subclases a campos que quizá quieras cambiar. - Esperar que
privatesea una característica de seguridad. Es una regla de tiempo de compilación que evita el acceso accidental, no cifrado. Los bytes siguen en memoria;privatetrata sobre diseño limpio, no sobre secretismo.
Siguiente: Structs
Ya has visto que un struct es en realidad solo una class cuyos miembros son public por defecto. La siguiente página, structs, profundiza en cuándo ese valor por defecto público es exactamente lo que quieres (agregados ligeros para agrupar valores relacionados) y cómo se usa struct en el C++ idiomático junto a clases con todas las prestaciones.
Preguntas frecuentes
¿Cuál es la diferencia entre public, private y protected en C++?
Los miembros public son accesibles desde cualquier sitio. Los miembros private solo son accesibles desde dentro de la misma clase (y sus friends). protected es como private, pero además permite que las clases derivadas accedan al miembro. Por convención, mantienes los datos private y expones el comportamiento a través de métodos public.
¿Los miembros son privados por defecto en C++?
En una class, sí: todo es private hasta que escribes un especificador de acceso. En un struct, el valor por defecto es public. Ese único valor por defecto es la verdadera diferencia entre class y struct en C++; ambos pueden tener métodos, constructores y especificadores de acceso.
¿Qué hace la palabra clave friend en C++?
friend concede a una función o clase concreta acceso a tus miembros private y protected. Es una excepción deliberada y acotada al encapsulamiento: la clase nombra exactamente en quién confía, de modo que el acceso nunca se concede de forma implícita.