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 fuera de su clase necesita acceder a sus miembros privados. En lugar de hacer que esos miembros sean públicos, C++ proporciona la palabra clave friend para otorgar acceso selectivo manteniendo el encapsulamiento.
Una función amiga se declara dentro de una clase, pero no es un 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) {}
// Declarar printWidth como friend
friend void printWidth(const Box& b);
};
// Definición de la 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á definida fuera de la clase sin el operador de resolución de ámbito. Es una función regular que resulta tener privilegios de acceso especiales.
También puedes hacer que una clase entera sea amiga, otorgando a todas sus funciones miembro acceso a los miembros privados:
class Engine {
int horsepower;
friend class Mechanic; // Mechanic puede acceder a miembros privados
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Permitido - Mechanic es una clase amiga
}
};La amistad no es mutua: si Engine declara a Mechanic como amigo, Mechanic puede acceder a los miembros privados de Engine, pero no viceversa. Usa los amigos con moderación, ya que crean un acoplamiento fuerte entre clases. Son especialmente útiles para la sobrecarga de operadores y funciones auxiliares estrechamente relacionadas.
Desafío
FácilVamos a construir un sistema de mensajería segura donde una clase MessageReader necesita acceso especial para leer mensajes cifrados almacenados en una clase SecureMessage. Este es un escenario perfecto para usar funciones amigas (friend functions) y clases amigas (friend classes).
Crearás dos archivos para organizar tu código:
SecureMessage.h: Define una claseSecureMessageque almacena datos de mensajes privados y otorga acceso selectivo a entidades de confianza. Tu clase debe tener:- Miembros privados:
content(string) ysecretKey(int) - Un constructor que reciba el contenido y la clave secreta
- Declarar
MessageReadercomo una 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"
Luego define la clase
MessageReaderen el mismo archivo de cabecera:- Un método
readContent(const SecureMessage& msg)que acceda directamente y devuelva elcontentprivado del mensaje - Un método
verifyKey(const SecureMessage& msg, int key)que verifique si la clave proporcionada coincide con lasecretKeydel mensaje y devuelva"Valid"o"Invalid"
Finalmente, define la función amiga
decryptMessagedespués de las clases.- Miembros privados:
main.cpp: Demuestra tanto las funciones amigas como las clases amigas en acción. Lee el contenido de un mensaje, una clave secreta y una clave de prueba de la entrada (cada una en una línea separada), luego:- 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 regulares 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 tu archivo de cabecera en main.cpp usando #include "SecureMessage.h".
Hoja de referencia
La palabra clave friend otorga acceso selectivo a miembros privados mientras mantiene el encapsulamiento.
Función Amiga (Friend Function): Una función declarada dentro de una clase que puede acceder a sus miembros privados y protegidos, pero no es un miembro de la clase:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Declarar función amiga
friend void printWidth(const Box& b);
};
// Definir fuera de la clase sin el operador de resolución de ámbito
void printWidth(const Box& b) {
std::cout << b.width; // Puede acceder al miembro privado
}Clase Amiga (Friend Class): Una clase entera puede ser declarada como amiga, otorgando a todas sus funciones miembro acceso a los miembros privados:
class Engine {
int horsepower;
friend class Mechanic; // Todos los métodos de Mechanic pueden acceder a los miembros privados
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Permitido
}
};Puntos Clave:
- La amistad no es mutua: si A declara a B como amigo, B puede acceder a los miembros privados de A, pero no al revés
- Las funciones amigas se definen fuera de la clase sin el operador de resolución de ámbito
- Use las funciones amigas con moderación, ya que crean un acoplamiento fuerte entre las clases
- Son más útiles para la sobrecarga de operadores y funciones auxiliares estrechamente relacionadas
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "SecureMessage.h"
using namespace std;
int main() {
// Leer la 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: Crear un SecureMessage con el contenido y la secret key
// TODO: Crear un objeto MessageReader
// TODO: Imprimir "Reader access: <content>" usando readContent()
// TODO: Imprimir "Key verification: <result>" usando verifyKey() con test key
// TODO: Imprimir "Decrypt with test key: <result>" usando decryptMessage con test key
// TODO: Imprimir "Decrypt with correct key: <result>" usando decryptMessage con 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érico