C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
123: PIMPL Idiom for ABI Stability
Imagine you're ordering a meal from a high-end restaurant. You have a menu that lists the dishes you can order and what's in them. That menu is the "public interface." Now, imagine if that menu also included a detailed blueprint of the kitchen—where the fryers are located, which brand of oven the chef uses, and exactly how the dishwasher is plumbed into the wall. If the restaurant decided to upgrade their oven or move the fridge three feet to the left, they'd have to reprint every single menu in the building and hand them out to every customer just so the customers would know the kitchen layout changed. That's absurd, right? You don't care about the oven; you just want the steak.
In C++, our header files are the menus. When you put private member variables directly in a class definition in a header, you're essentially putting the kitchen blueprint on the menu. Any time you add a new private variable or change a private data type, every single file that #includes that header has to be recompiled. Even worse, if you're shipping a compiled library (a DLL or .so file), changing the size of the class breaks the ABI (Application Binary Interface). The client's code expects the class to be X bytes, but you've suddenly made it Y bytes. Crash. Boom.
Stopping the Recompile Domino Effect
The PIMPL idiom—which stands for "Pointer to IMPLementation"—solves this by moving all the "kitchen equipment" into a separate structure that is completely hidden from the user. Instead of having five private member variables in your header, you have exactly one: a pointer to a hidden struct.
Let's look at a NetworkManager. Usually, this class would be littered with socket handles, buffer arrays, and perhaps some platform-specific headers like winsock2.h that you absolutely do not want leaking into your user's code.
// NetworkManager.h
#include <memory>
#include <string>
class NetworkManager {
public:
NetworkManager();
~NetworkManager(); // We need a custom destructor for PIMPL!
void connect(const std::string& ip);
void sendData(const std::string& data);
private:
struct Impl; // Forward declaration of the implementation
std::unique_ptr<Impl> pImpl; // The only private member
};
Notice how the header is now incredibly "thin." The user doesn't know what's inside Impl. They don't even know it's a struct; it's just an opaque type to them. This means I can add, remove, or change any variable inside that Impl struct in the .cpp file, and as long as the public function signatures stay the same, the user never has to recompile their code.
Defining the Hidden Kitchen
The actual work happens in the implementation file. This is where we define exactly what the Impl struct looks like. I usually inherit from the public class or just make it a standalone struct that holds the state.
// NetworkManager.cpp
#include "NetworkManager.h"
#include <vector>
#include <iostream>
// Imagine a heavy, platform-specific header here
// #include <sys/socket.h>
struct NetworkManager::Impl {
int socketHandle = -1;
std::vector<char> readBuffer;
std::string currentIp;
void internalLog(const std::string& msg) {
std::cout < "[NetworkLog] " < msg < "\n";
}
};
NetworkManager::NetworkManager() : pImpl(std::make_unique<Impl>()) {}
// This is crucial: The destructor must be defined here because
// std::unique_ptr needs to see the full definition of Impl to delete it.
NetworkManager::~NetworkManager() = default;
void NetworkManager::connect(const std::string& ip) {
pImpl->currentIp = ip;
pImpl->internalLog("Connecting to " + ip);
// Real socket logic goes here...
}
void NetworkManager::sendData(const std::string& data) {
pImpl->internalLog("Sending: " + data);
// Use pImpl->socketHandle to send...
}
The Cost of Hiding
I won't tell you this is a free lunch. There are two main trade-offs you need to be aware of. First, you're adding a heap allocation via std::unique_ptr and a pointer indirection every time you access a member variable. In 99% of application code, this is negligible. If you're writing a high-frequency trading engine or a physics kernel, maybe it matters. For everything else, it doesn't.
Second, you have to be careful with the "Rule of Five." Because you're using a std::unique_ptr, your class is now non-copyable by default. If you want users to be able to copy your NetworkManager, you'll have to manually implement the copy constructor and copy assignment operator to perform a "deep copy" of the Impl struct. If you don't need copying, just = delete them and call it a day.
📋 Practical Task
Refactoring the ImageProcessor for ABI Stability
You have been handed a legacy ImageProcessor class. The problem is that its header file includes a massive third-party library header <heavy_image_lib.h>, which is slowing down the compile times of the entire project and forcing users to have the library installed just to include the header.
Your Task: Refactor the following code to use the PIMPL idiom. You must move all private members and the #include <heavy_image_lib.h> out of the header and into the implementation file.
// CURRENT HEADER (ImageProcessor.h)
#pragma once
#include <string>
#include <heavy_image_lib.h> // This is the problem!
class ImageProcessor {
public:
ImageProcessor();
~ImageProcessor();
void loadFile(const std::string& path);
void applyGrayscale();
private:
HeavyImageBuffer buffer; // Part of the heavy library
int qualitySettings = 90;
std::string lastLoadedPath;
};
Requirements:
- The
ImageProcessor.hfile must no longer include<heavy_image_lib.h>. - Use
std::unique_ptrfor the implementation pointer. - Ensure the destructor is correctly handled to avoid compilation errors regarding incomplete types.
- The public API (
loadFileandapplyGrayscale) must remain unchanged.
There are no comments for now.