Idioma Pimpl
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 78 de 104.
El idioma Pimpl (Puntero a la implementación) es una técnica que oculta los detalles de implementación de una clase trasladándolos a una clase independiente declarada previamente. Esto reduce las dependencias de compilación y mantiene los miembros privados verdaderamente ocultos del archivo de encabezado.
La idea central es sencilla: en lugar de declarar miembros privados directamente en tu clase, declaras un puntero a una clase de implementación que se define únicamente en el archivo de código fuente:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // Declaración anticipada
std::unique_ptr<Impl> pImpl;
};// Widget.cpp
#include "Widget.h"
#include <iostream>
class Widget::Impl {
public:
int data = 42;
void process() { std::cout << "Processing: " << data << "\n"; }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() { pImpl->process(); }Los beneficios clave son el cortafuegos de compilación y la compatibilidad binaria. Cuando cambias la clase Impl, solo es necesario recompilar el archivo fuente, no cada archivo que incluye el header. Esto acelera drásticamente los tiempos de compilación en proyectos grandes.
Ten en cuenta que el destructor debe definirse en el archivo de origen (incluso si se usa el valor predeterminado) porque unique_ptr necesita el tipo completo de Impl para eliminarlo. Este es un error común al usar Pimpl por primera vez.
Desafío
FácilConstruyamos un gestor de mensajes seguro utilizando el idiom Pimpl para ocultar los detalles de implementación del cifrado del archivo de encabezado. Esto demuestra cómo Pimpl crea una barrera de compilación: cualquiera que incluya tu encabezado no verá el funcionamiento interno del procesamiento de mensajes.
Organizarás tu código en tres archivos:
SecureMessage.h: Define la interfaz pública de tu claseSecureMessage.Tu clase debe tener una clase
Impldeclarada previamente y unstd::unique_ptrque apunte a ella. La interfaz pública debe incluir:- Un constructor que reciba un
const std::string¶ el mensaje original - Un destructor (debe declararse aquí y definirse en el archivo .cpp)
- Un método
setKey(int key)para establecer una clave de cifrado - Un método
getEncrypted()que devuelva el mensaje cifrado como unstd::string - Un método
getOriginal()que devuelva el mensaje original
El encabezado solo debe mostrar la interfaz pública: aquí no deben ser visibles detalles de implementación sobre cómo funciona el cifrado.
- Un constructor que reciba un
SecureMessage.cpp: Define la clase anidadaImple implementa todos los métodos.Tu clase
Impldebe almacenar el mensaje original, la clave de cifrado (con un valor predeterminado de 0) y encargarse de la lógica de cifrado real. Utiliza un cifrado César simple para el cifrado: desplaza cada carácter según el valor de la clave. Por ejemplo, con la clave 3, 'a' se convierte en 'd' y 'z' vuelve a empezar en 'c'.El cifrado solo debe afectar a las letras minúsculas (a-z), dejando todos los demás caracteres sin cambios. Recuerda definir aquí el destructor (aunque sea predeterminado), ya que
unique_ptrnecesita que el tipoImplesté completo.main.cpp: Lee dos entradas:- Una cadena de mensaje (puede contener espacios)
- Una clave de cifrado (entero)
Crea un objeto
SecureMessage, establece la clave y muestra los resultados:- Imprime
Original:seguido del mensaje original - Imprime
Encrypted:seguido del mensaje cifrado
Por ejemplo, con las entradas hello world y 3:
Original: hello world
Encrypted: khoor zruogCon las entradas xyz abc y 5:
Original: xyz abc
Encrypted: cde fghObserva cómo el archivo de encabezado no revela nada sobre la implementación del cifrado César: ese es el poder de Pimpl. Si más adelante cambiaras a un algoritmo de cifrado diferente, solo sería necesario recompilar SecureMessage.cpp, no ningún archivo que incluya el encabezado.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// Lee el mensaje (puede contener espacios)
std::string message;
std::getline(std::cin, message);
// Lee la clave de cifrado
int key;
std::cin >> key;
// TODO: Crea un objeto SecureMessage con el mensaje
// TODO: Establece la clave de cifrado
// TODO: Imprime "Original: " seguido del mensaje original
// TODO: Imprime "Encrypted: " seguido del mensaje cifrado
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