C/C++ Arena

Step 4 of 7

Pimpl

In a big codebase, a header is included by hundreds of files. Changing a class's private members changes its header, so every one of those files must recompile, and in a shared library it breaks binary compatibility with programs built against the old version. The pimpl idiom ("pointer to implementation") hides the private data behind a pointer to a struct that the header only declares.

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// ---- counter.h: what users see ----
class WordCounter {
public:
    WordCounter();
    ~WordCounter();
    void add(const std::string& w);
    int distinct() const;

private:
    struct Impl;                         // declared, not defined: "incomplete"
    std::unique_ptr<Impl> impl_;
};

// ---- counter.cpp: private details, free to change ----
struct WordCounter::Impl {
    std::vector<std::string> seen;
};

WordCounter::WordCounter() : impl_(std::make_unique<Impl>()) {}
WordCounter::~WordCounter() = default;   // Impl is complete here

void WordCounter::add(const std::string& w) {
    for (const auto& s : impl_->seen) if (s == w) return;
    impl_->seen.push_back(w);
}

int WordCounter::distinct() const { return (int)impl_->seen.size(); }

int main() {
    WordCounter c;
    for (const char* w : {"to", "be", "or", "not", "to", "be"}) c.add(w);
    std::cout << c.distinct() << "\n";
}
4

How it works

The destructor trap

unique_ptr<Impl> must know Impl's full definition to delete it. If you let the compiler generate the destructor in the header, it's generated where Impl is incomplete, and you get a confusing error about sizeof of an incomplete type. The fix is to declare the destructor in the header and define it (even as = default) in the .cpp file, after Impl. The same applies to the move constructor and move assignment.

Your task

Define struct Roster::Impl with a std::vector<std::string>, then the constructor (create the Impl with make_unique), Roster::~Roster() = default;, Roster::Roster(Roster&&) noexcept = default;, and add and count working through impl_.

Your turn: the "header" part is written. Write the "source file" part: define Impl (holding a std::vector<std::string> of names), the constructor, destructor, move constructor, add and count.

Previous: Observers with weak_ptr Next: RAII wrappers for C libraries