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 (Pointer to Implementation) es una técnica que oculta los detalles de implementación de una clase moviéndolos a una clase separada, declarada de forma anticipada. Esto reduce las dependencias de compilación y mantiene los miembros privados verdaderamente ocultos del archivo de cabecera.
La idea central es sencilla: en lugar de declarar miembros privados directamente en su clase, se declara un puntero a una clase de implementación que se define únicamente en el archivo de origen:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // Declaración adelantada
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 el archivo fuente necesita recompilación, no todos los archivos que incluyen el encabezado. 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 es por defecto) 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ácilVamos a construir un manejador de mensajes seguros utilizando el idioma Pimpl para ocultar los detalles de implementación del cifrado del archivo de cabecera. Esto demuestra cómo Pimpl crea un cortafuegos de compilación: cualquier persona que incluya tu cabecera no verá el funcionamiento interno del procesamiento de tus mensajes.
Organizarás tu código en tres archivos:
SecureMessage.h: Define la interfaz pública para tu claseSecureMessage.Tu clase debe tener una clase
Impldeclarada de forma anticipada (forward-declared) y unstd::unique_ptrhacia ella. La interfaz pública debe incluir:- Un constructor que reciba un
const std::string¶ el mensaje original - Un destructor (debe ser declarado aquí, definido 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
La cabecera solo debe mostrar la interfaz pública; no debe haber detalles de implementación sobre cómo funciona el cifrado visibles aquí.
- 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 (por defecto 0) y manejar la lógica de cifrado real. Utiliza un cifrado César simple para el encriptado: desplaza cada carácter según el valor de la clave. Por ejemplo, con la clave 3, 'a' se convierte en 'd', 'z' da la vuelta a 'c'.El cifrado solo debe afectar a las letras minúsculas (a-z), dejando todos los demás caracteres sin cambios. Recuerda definir el destructor aquí (incluso si es por defecto) ya que
unique_ptrnecesita el tipoImplcompleto.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 cabecera 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 SecureMessage.cpp necesitaría ser recompilado, no cualquier archivo que incluya la cabecera.
Hoja de referencia
El idioma Pimpl (Pointer to Implementation - Puntero a la Implementación) oculta los detalles de implementación moviéndolos a una clase separada definida solo en el archivo de origen, creando un cortafuegos de compilación.
Estructura básica con declaración anticipada (forward declaration) en el encabezado:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // Debe declararse en el encabezado, definirse en .cpp
void doSomething();
private:
class Impl; // Declaración anticipada
std::unique_ptr<Impl> pImpl;
};Implementación en el archivo de origen:
// Widget.cpp
#include "Widget.h"
class Widget::Impl {
public:
int data = 42;
void process() { /* implementación */ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // Debe definirse en .cpp
void Widget::doSomething() { pImpl->process(); }Puntos clave:
- El destructor debe definirse en el archivo de origen (incluso si es por defecto) porque
unique_ptrnecesita el tipo completo deImplpara eliminarlo - Los cambios en
Implsolo requieren recompilar el archivo de origen, no los archivos que incluyen el encabezado - Proporciona un cortafuegos de compilación y compatibilidad binaria
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érico