Pimpl Idiom
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 78 of 104.
The Pimpl idiom (Pointer to Implementation) is a technique that hides a class's implementation details by moving them into a separate, forward-declared class. This reduces compilation dependencies and keeps private members truly hidden from the header file.
The core idea is simple: instead of declaring private members directly in your class, you declare a pointer to an implementation class that's defined only in the source file:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // Forward declaration
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(); }The key benefits are compilation firewall and binary compatibility. When you change the Impl class, only the source file needs recompilation - not every file that includes the header. This dramatically speeds up build times in large projects.
Note that the destructor must be defined in the source file (even if defaulted) because unique_ptr needs the complete type of Impl to delete it. This is a common pitfall when first using Pimpl.
Challenge
EasyLet's build a secure message handler using the Pimpl idiom to hide the encryption implementation details from the header file. This demonstrates how Pimpl creates a compilation firewall—anyone including your header won't see the internal workings of your message processing.
You'll organize your code across three files:
SecureMessage.h: Define the public interface for yourSecureMessageclass.Your class should have a forward-declared
Implclass and astd::unique_ptrto it. The public interface should include:- A constructor that takes a
const std::string&for the original message - A destructor (must be declared here, defined in the .cpp file)
- A
setKey(int key)method to set an encryption key - A
getEncrypted()method that returns the encrypted message as astd::string - A
getOriginal()method that returns the original message
The header should only show the public interface—no implementation details about how encryption works should be visible here.
- A constructor that takes a
SecureMessage.cpp: Define the nestedImplclass and implement all methods.Your
Implclass should store the original message, the encryption key (default to 0), and handle the actual encryption logic. Use a simple Caesar cipher for encryption: shift each character by the key value. For example, with key 3, 'a' becomes 'd', 'z' wraps around to 'c'.The encryption should only affect lowercase letters (a-z), leaving all other characters unchanged. Remember to define the destructor here (even if defaulted) since
unique_ptrneeds the completeImpltype.main.cpp: Read two inputs:- A message string (may contain spaces)
- An encryption key (integer)
Create a
SecureMessageobject, set the key, and display the results:- Print
Original:followed by the original message - Print
Encrypted:followed by the encrypted message
For example, with inputs hello world and 3:
Original: hello world
Encrypted: khoor zruogWith inputs xyz abc and 5:
Original: xyz abc
Encrypted: cde fghNotice how the header file reveals nothing about the Caesar cipher implementation—that's the power of Pimpl. If you later changed to a different encryption algorithm, only SecureMessage.cpp would need recompilation, not any file that includes the header.
Cheat sheet
The Pimpl idiom (Pointer to Implementation) hides implementation details by moving them to a separate class defined only in the source file, creating a compilation firewall.
Basic structure with forward declaration in header:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // Must be declared in header, defined in .cpp
void doSomething();
private:
class Impl; // Forward declaration
std::unique_ptr<Impl> pImpl;
};Implementation in source file:
// Widget.cpp
#include "Widget.h"
class Widget::Impl {
public:
int data = 42;
void process() { /* implementation */ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // Must be defined in .cpp
void Widget::doSomething() { pImpl->process(); }Key points:
- The destructor must be defined in the source file (even if defaulted) because
unique_ptrneeds the complete type ofImplto delete it - Changes to
Implonly require recompiling the source file, not files that include the header - Provides compilation firewall and binary compatibility
Try it yourself
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// Read the message (may contain spaces)
std::string message;
std::getline(std::cin, message);
// Read the encryption key
int key;
std::cin >> key;
// TODO: Create a SecureMessage object with the message
// TODO: Set the encryption key
// TODO: Print "Original: " followed by the original message
// TODO: Print "Encrypted: " followed by the encrypted message
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container