Pimpl Idiom
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 78 de 104.
O idioma Pimpl (Pointer to Implementation) é uma técnica que esconde os detalhes de implementação de uma classe, movendo-os para uma classe separada, declarada antecipadamente. Isso reduz as dependências de compilação e mantém os membros privados verdadeiramente ocultos do arquivo de cabeçalho.
A ideia central é simples: em vez de declarar membros privados diretamente em sua classe, você declara um ponteiro para uma classe de implementação que é definida apenas no arquivo de origem:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // Declaração antecipada
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(); }Os principais benefícios são firewall de compilação e compatibilidade binária. Quando você altera a classe Impl, apenas o arquivo de origem precisa de recompilação — não todos os arquivos que incluem o cabeçalho. Isso acelera drasticamente os tempos de compilação em grandes projetos.
Observe que o destrutor deve ser definido no arquivo de origem (mesmo que seja o padrão) porque unique_ptr precisa do tipo completo de Impl para excluí-lo. Esta é uma armadilha comum ao usar Pimpl pela primeira vez.
Desafio
FácilVamos construir um manipulador de mensagens seguro usando o idioma Pimpl para ocultar os detalhes de implementação da criptografia do arquivo de cabeçalho. Isso demonstra como o Pimpl cria um firewall de compilação — qualquer pessoa que inclua seu cabeçalho não verá o funcionamento interno do processamento de sua mensagem.
Você organizará seu código em três arquivos:
SecureMessage.h: Defina a interface pública para sua classeSecureMessage.Sua classe deve ter uma classe
Impldeclarada antecipadamente (forward-declared) e umstd::unique_ptrpara ela. A interface pública deve incluir:- Um construtor que recebe uma
const std::string¶ a mensagem original - Um destrutor (deve ser declarado aqui, definido no arquivo .cpp)
- Um método
setKey(int key)para definir uma chave de criptografia - Um método
getEncrypted()que retorna a mensagem criptografada como umastd::string - Um método
getOriginal()que retorna a mensagem original
O cabeçalho deve mostrar apenas a interface pública — nenhum detalhe de implementação sobre como a criptografia funciona deve ser visível aqui.
- Um construtor que recebe uma
SecureMessage.cpp: Defina a classeImplaninhada e implemente todos os métodos.Sua classe
Impldeve armazenar a mensagem original, a chave de criptografia (padrão como 0) e lidar com a lógica de criptografia real. Use uma cifra de César simples para a criptografia: desloque cada caractere pelo valor da chave. Por exemplo, com a chave 3, 'a' torna-se 'd', 'z' volta para 'c'.A criptografia deve afetar apenas letras minúsculas (a-z), deixando todos os outros caracteres inalterados. Lembre-se de definir o destrutor aqui (mesmo que seja o padrão) já que o
unique_ptrprecisa do tipoImplcompleto.main.cpp: Leia duas entradas:- Uma string de mensagem (pode conter espaços)
- Uma chave de criptografia (inteiro)
Crie um objeto
SecureMessage, defina a chave e exiba os resultados:- Imprima
Original:seguido pela mensagem original - Imprima
Encrypted:seguido pela mensagem criptografada
Por exemplo, com as entradas hello world e 3:
Original: hello world
Encrypted: khoor zruogCom as entradas xyz abc e 5:
Original: xyz abc
Encrypted: cde fghObserve como o arquivo de cabeçalho não revela nada sobre a implementação da cifra de César — esse é o poder do Pimpl. Se você mudasse posteriormente para um algoritmo de criptografia diferente, apenas o SecureMessage.cpp precisaria de recompilação, não qualquer arquivo que inclua o cabeçalho.
Folha de consulta
O idioma Pimpl (Pointer to Implementation) oculta detalhes de implementação movendo-os para uma classe separada definida apenas no arquivo de origem, criando um firewall de compilação.
Estrutura básica com declaração antecipada (forward declaration) no cabeçalho:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // Deve ser declarado no cabeçalho, definido no .cpp
void doSomething();
private:
class Impl; // Declaração antecipada
std::unique_ptr<Impl> pImpl;
};Implementação no arquivo de origem:
// Widget.cpp
#include "Widget.h"
class Widget::Impl {
public:
int data = 42;
void process() { /* implementação */ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // Deve ser definido no .cpp
void Widget::doSomething() { pImpl->process(); }Pontos-chave:
- O destrutor deve ser definido no arquivo de origem (mesmo que seja o padrão/default) porque o
unique_ptrprecisa do tipo completo deImplpara excluí-lo - Alterações em
Implexigem apenas a recompilação do arquivo de origem, não dos arquivos que incluem o cabeçalho - Fornece firewall de compilação e compatibilidade binária
Experimente você mesmo
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// Lê a mensagem (pode conter espaços)
std::string message;
std::getline(std::cin, message);
// Lê a chave de criptografia
int key;
std::cin >> key;
// TODO: Criar um objeto SecureMessage com a mensagem
// TODO: Definir a chave de criptografia
// TODO: Imprimir "Original: " seguido pela mensagem original
// TODO: Imprimir "Encrypted: " seguido pela mensagem criptografada
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico