Especificadores de acceso en profundidad
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 35 de 104.
Ahora que entiendes los tres especificadores de acceso, exploremos cómo se comportan cuando tienes múltiples objetos de la misma clase. Un error común es pensar que private significa "solo este objeto", pero en realidad significa "solo esta clase".
Los objetos de la misma clase pueden acceder a los miembros privados de los demás:
class Person {
std::string secret;
public:
Person(std::string s) : secret(s) {}
void readSecret(const Person& other) {
// ¡Acceder al miembro privado de otro objeto está permitido!
std::cout << other.secret;
}
};Esto funciona porque el control de acceso se aplica a nivel de clase, no a nivel de objeto. El método readSecret es parte de la clase Person, por lo que puede acceder a los miembros privados de cualquier objeto Person. Este diseño permite que operaciones como los constructores de copia y los operadores de comparación funcionen con el estado interno de otros objetos.
También puedes alternar entre especificadores de acceso varias veces dentro de una clase:
class Mixed {
public:
void publicMethod1();
private:
int privateData;
public:
void publicMethod2(); // De vuelta a público
private:
void privateHelper(); // Privado de nuevo
};Si bien esto es C++ válido, generalmente es mejor agrupar los miembros por nivel de acceso para mejorar la legibilidad. La mayoría de las guías de estilo recomiendan listar primero los miembros públicos (ya que forman la interfaz de la clase), seguidos de los protegidos y, por último, los privados.
Desafío
FácilVamos a construir un sistema de comparación de billeteras que demuestre cómo los objetos de la misma clase pueden acceder a los miembros privados de los demás. Esta es una idea clave sobre el control de acceso en C++: se aplica a nivel de clase, no a nivel de objeto.
Crearás dos archivos para organizar tu código:
Wallet.h: Define una claseWalletque almacene datos financieros privados pero permita comparaciones entre billeteras. Tu clase debe tener:- Miembros privados:
ownerName(string) ybalance(double) - Un constructor que reciba el nombre del propietario y el saldo inicial
- Un getter público
getOwnerName()que devuelva el nombre del propietario - Un método
hasMoreThan(const Wallet& other)que compare el saldo de esta billetera con el saldo privado de otra billetera, devolviendotruesi esta billetera tiene más dinero - Un método
combinedBalance(const Wallet& other)que devuelva la suma de los saldos privados de ambas billeteras - Un método
transferTo(Wallet& other, double amount)que mueva dinero de esta billetera a otra; debe modificar directamente los saldos privados de ambas billeteras (solo si el monto es positivo y no excede el saldo de esta billetera)
- Miembros privados:
main.cpp: Demuestra el acceso a miembros privados entre objetos. Lee dos nombres de propietarios y dos saldos de la entrada (cuatro líneas: name1, balance1, name2, balance2), luego:- Crea dos objetos
Walletcon los valores de entrada - Imprime
"Combined wealth: "Combined wealth: $usando"lt;amount>" combinedBalance() - Compara las billeteras e imprime
"<name> has more money"para el propietario que tenga el saldo más alto (usahasMoreThan()para determinar esto) - Transfiere
100.0de la primera billetera a la segunda usandotransferTo() - Imprime
"After transfer:" - Imprime
"Combined wealth: "Combined wealth: $de nuevo (debería permanecer sin cambios)"lt;amount>" - Compara de nuevo e imprime
"<name> has more money"(el resultado puede haber cambiado)
- Crea dos objetos
El concepto clave aquí es que los métodos como hasMoreThan() y transferTo() pueden acceder directamente al miembro privado balance de otro objeto Wallet, sin necesidad de getters. Esto funciona porque el control de acceso en C++ se basa en la clase: cualquier código dentro de la clase Wallet puede acceder a los miembros privados de cualquier objeto Wallet.
Formatea todos los valores monetarios con dos decimales usando std::fixed y std::setprecision(2) de <iomanip>. Convierte las entradas de saldo usando std::stod().
Hoja de referencia
El control de acceso en C++ se aplica a nivel de clase, no a nivel de objeto. Esto significa que los objetos de la misma clase pueden acceder a los miembros privados de los demás:
class Person {
std::string secret;
public:
Person(std::string s) : secret(s) {}
void readSecret(const Person& other) {
// Accediendo al miembro privado de otro objeto - ¡permitido!
std::cout << other.secret;
}
};El método readSecret puede acceder al miembro privado secret de cualquier objeto Person porque forma parte de la clase Person. Este diseño permite operaciones como constructores de copia y operadores de comparación.
Puedes alternar entre especificadores de acceso varias veces dentro de una clase:
class Mixed {
public:
void publicMethod1();
private:
int privateData;
public:
void publicMethod2(); // De vuelta a público
private:
void privateHelper(); // Privado de nuevo
};Sin embargo, es una mejor práctica agrupar los miembros por nivel de acceso para mejorar la legibilidad. La mayoría de las guías de estilo recomiendan listar primero los miembros públicos, seguidos de los protegidos y luego los privados.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <iomanip>
#include "Wallet.h"
using namespace std;
int main() {
// Leer entrada: name1, balance1, name2, balance2
string name1, name2;
string balanceStr1, balanceStr2;
getline(cin, name1);
getline(cin, balanceStr1);
getline(cin, name2);
getline(cin, balanceStr2);
double balance1 = stod(balanceStr1);
double balance2 = stod(balanceStr2);
// Establecer el formato de salida para valores monetarios
cout << fixed << setprecision(2);
// TODO: Crear dos objetos Wallet con los valores de entrada
// TODO: Imprimir la riqueza combinada usando combinedBalance()
// Formato: "Combined wealth: $<amount>"
// TODO: Comparar las billeteras usando hasMoreThan() e imprimir quién tiene más dinero
// Formato: "<name> has more money"
// TODO: Transferir 100.0 de la primera billetera a la segunda usando transferTo()
// TODO: Imprimir "After transfer:"
// TODO: Imprimir la riqueza combinada de nuevo
// TODO: Comparar de nuevo e imprimir quién tiene más dinero
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