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 varios objetos de la misma clase. Una idea errónea común es 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 en el nivel de clase, no en el nivel del objeto. El método readSecret forma 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 trabajen con el estado interno de otros objetos.
También puedes alternar entre los especificadores de acceso varias veces dentro de una clase:
class Mixed {
public:
void publicMethod1();
private:
int privateData;
public:
void publicMethod2(); // De nuevo público
private:
void privateHelper(); // De nuevo privado
};Aunque esto es válido en C++, por lo general es mejor agrupar los miembros por nivel de acceso para facilitar la lectura. La mayoría de las guías de estilo recomiendan enumerar primero los miembros públicos (ya que forman la interfaz de la clase), seguidos de los protegidos y, después, los privados.
Desafío
FácilConstruyamos un sistema de comparación de monederos que demuestre cómo los objetos de una misma clase pueden acceder a los miembros privados de los demás. Esta es una idea clave sobre el control de acceso de 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 almacena datos financieros privados, pero permite comparar monederos entre sí. Tu clase debe tener:- Miembros privados:
ownerName(cadena) ybalance(double) - Un constructor que recibe el nombre del propietario y el saldo inicial
- Un getter público
getOwnerName()que devuelve el nombre del propietario - Un método
hasMoreThan(const Wallet& other)que compara el saldo de este monedero con el saldo privado de otro monedero y devuelvetruesi este monedero tiene más dinero - Un método
combinedBalance(const Wallet& other)que devuelve la suma de los saldos privados de ambos monederos - Un método
transferTo(Wallet& other, double amount)que transfiere dinero de este monedero a otro. Debe modificar directamente los saldos privados de ambos monederos (solo si el importe es positivo y no supera el saldo de este monedero)
- Miembros privados:
main.cpp: Demuestra el acceso a miembros privados de otros objetos. Lee dos nombres de propietarios y dos saldos de la entrada (cuatro líneas: name1, balance1, name2, balance2) y luego:- Crea dos objetos
Walletcon los valores de entrada - Imprime
"Combined wealth: $<amount>"usandocombinedBalance() - Compara los monederos e imprime
"<name> has more money"para el propietario que tenga el saldo más alto (usahasMoreThan()para determinarlo) - Transfiere
100.0del primer monedero al segundo usandotransferTo() - Imprime
"After transfer:" - Imprime de nuevo
"Combined wealth: $<amount>"(debería permanecer sin cambios) - Vuelve a comparar e imprime
"<name> has more money"(el resultado puede haber cambiado)
- Crea dos objetos
El concepto clave aquí es que métodos como hasMoreThan() y transferTo() pueden acceder directamente al miembro privado balance de otro objeto Wallet: no hacen falta getters. Esto funciona porque el control de acceso en C++ se basa en clases: cualquier código dentro de la clase Wallet puede acceder a los miembros privados de cualquier objeto Wallet.
Da formato a todos los valores monetarios con dos decimales usando std::fixed y std::setprecision(2) de <iomanip>. Convierte los saldos de entrada usando std::stod().
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <iomanip>
#include "Wallet.h"
using namespace std;
int main() {
// Lee la 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);
// Configura el formato de salida para los valores monetarios
cout << fixed << setprecision(2);
// TODO: Crea dos objetos Wallet con los valores de entrada
// TODO: Imprime el patrimonio combinado usando combinedBalance()
// Formato: "Combined wealth: $<amount>"
// TODO: Compara las billeteras usando hasMoreThan() e imprime quién tiene más dinero
// Formato: "<name> has more money"
// TODO: Transfiere 100.0 de la primera billetera a la segunda usando transferTo()
// TODO: Imprime "After transfer:"
// TODO: Imprime de nuevo el patrimonio combinado
// TODO: Compara de nuevo e imprime 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 la POO
Archivos externosCompilación y construcción en C++Archivos de cabecera y archivos fuenteEspacios de nombres y ámbitoIntroducción a la POO en C++Clases y objetosEl puntero 'this'Métodos (funciones miembro)Atributos (miembros de datos)Fundamentos de constructores y destructoresRepaso: calculadora sencilla4Propiedades de clase
Miembros de instancia vs. estáticosGetters y settersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases friendRepaso: gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de constructores y destructoresSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualRepaso: jerarquía de empleados2Gestión de memoria
Memoria de pila vs. heapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Repaso: gestor de arreglos dinámicos5Encapsulación
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultación de informaciónStruct vs. classClases anidadas e internasRepaso: sistema de registros de estudiantes8Polimorfismo
Polimorfismo en compilación y en tiempo de ejecuciónSobrecarga de funcionesRepaso de las funciones virtualesFunciones virtuales purasClases abstractasDiseño de interfaces en C++Conversión dinámica y RTTIRepaso: calculadora de formas11Conceptos avanzados de POO
Composición frente a herenciaMixins mediante CRTPIdiom PimplBorrado de tiposClases enum 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 predeterminadoConstructor con parámetrosConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis detallado del destructorRegla de tres / cinco / ceroRepaso: clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujoSobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tipoRepaso: clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasFundamentos de SFINAE y rasgos de tiposRepaso: contenedor genéricoPractica por tu cuenta: Compilador de C++ online