Funciones y clases amigas
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 32 de 104.
A veces, una función o clase externa a tu clase necesita acceder a sus miembros privados. En lugar de hacer públicos esos miembros, C++ proporciona la palabra clave friend para conceder acceso selectivo y mantener la encapsulación.
Una función amiga se declara dentro de una clase, pero no es miembro de esa clase. Puede acceder a los miembros privados y protegidos de la clase:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Declara printWidth como friend
friend void printWidth(const Box& b);
};
// Definición de función friend - NO es una función miembro
void printWidth(const Box& b) {
std::cout << b.width; // Puede acceder al miembro privado
}Observa que printWidth está definido fuera de la clase sin el operador de resolución de ámbito. Es una función regular que casualmente tiene privilegios de acceso especiales.
También puedes hacer que toda una clase sea amiga, otorgando a todas sus funciones miembro acceso a los miembros privados:
class Engine {
int horsepower;
friend class Mechanic; // Mechanic puede acceder a los miembros privados
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Permitido - Mechanic es un friend
}
};La amistad no es mutua: si Engine declara a Mechanic como amigo, Mechanic puede acceder a los miembros privados de Engine, pero no al revés. Usa las amistades con moderación, ya que crean un acoplamiento estrecho entre las clases. Son más útiles para la sobrecarga de operadores y las funciones auxiliares estrechamente relacionadas.
Desafío
FácilConstruyamos un sistema de mensajería seguro en el que una clase MessageReader necesite acceso especial para leer mensajes cifrados almacenados en una clase SecureMessage. Este es un escenario perfecto para usar funciones amigas y clases amigas.
Crearás dos archivos para organizar tu código:
SecureMessage.h: Define una claseSecureMessageque almacene datos privados del mensaje y conceda acceso selectivo a entidades de confianza. Tu clase debe tener:- Miembros privados:
content(cadena) ysecretKey(entero) - Un constructor que reciba el contenido y la clave secreta
- Declarar
MessageReadercomo clase amiga para que pueda acceder a los miembros privados - Declarar una función amiga
decryptMessage(const SecureMessage& msg, int key)que devuelva el contenido solo si la clave proporcionada coincide con la clave secreta; de lo contrario, devuelve"Access Denied"
Después define la clase
MessageReaderen el mismo archivo de cabecera:- Un método
readContent(const SecureMessage& msg)que acceda directamente alcontentprivado del mensaje y lo devuelva - Un método
verifyKey(const SecureMessage& msg, int key)que compruebe si la clave proporcionada coincide con elsecretKeydel mensaje y devuelva"Valid"o"Invalid"
Finalmente, define la función amiga
decryptMessagedespués de las clases.- Miembros privados:
main.cpp: Demuestra en acción tanto las funciones amigas como las clases amigas. Lee el contenido de un mensaje, una clave secreta y una clave de prueba de la entrada (cada uno en una línea separada) y, después:- Crea un
SecureMessagecon el contenido y la clave secreta - Crea un objeto
MessageReader - Imprime
"Reader access: <content>"usando el métodoreadContent()del lector - Imprime
"Key verification: <result>"usandoverifyKey()con la clave de prueba - Imprime
"Decrypt with test key: <result>"usando la función amiga con la clave de prueba - Imprime
"Decrypt with correct key: <result>"usando la función amiga con la clave secreta original
- Crea un
Recuerda que las funciones amigas se definen fuera de la clase sin el operador de resolución de ámbito: son funciones normales con privilegios de acceso especiales. La clase MessageReader, al ser amiga, puede acceder directamente a los miembros privados de SecureMessage en todos sus métodos.
Convierte las entradas de las claves de cadenas a enteros usando std::stoi(). Incluye el archivo de cabecera en main.cpp usando #include "SecureMessage.h".
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "SecureMessage.h"
using namespace std;
int main() {
// Leer entrada
string messageContent;
string secretKeyStr;
string testKeyStr;
getline(cin, messageContent);
getline(cin, secretKeyStr);
getline(cin, testKeyStr);
int secretKey = stoi(secretKeyStr);
int testKey = stoi(testKeyStr);
// TODO: Crea un SecureMessage con el contenido y la clave secreta
// TODO: Crea un objeto MessageReader
// TODO: Print "Reader access: <content>" using readContent()
// TODO: Print "Key verification: <result>" using verifyKey() with test key
// TODO: Print "Decrypt with test key: <result>" using decryptMessage with test key
// TODO: Print "Decrypt with correct key: <result>" using decryptMessage with secret key
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éricoPractica por tu cuenta: Compilador de C++ online