Pimpl Idiom

pimpl.cpp

/*
 * pimpl.cpp
 *
 * The Pimpl Idiom with unique_ptr.
 * Scott Meyers "Effective Modern C++" Item 22.
 *
 * g++ -std=c++14 -Wpedantic -Wall -Wextra pimpl.cpp widget.cpp -o pimpl
 * 
 * Versions:
 * 1. Use a smart pointer instead of a raw pointer.
 *    error: invalid application of ‘sizeof’ to incomplete type ‘Widget::Impl
 *    When the Widget instances goes out of scope, a destructor is needed. The
 *    compiler generates one that calls the destructor of Widget's data member
 *    pImpl. But, as the compiler-generated destructor is implicitly inline,
 *    pImpl points to an incomplete type.
 * 2. Solution: Provide a destructor with definition in widget.cpp
 *    But declaring a destructor prevents generating the move operations.
 * 3. Solution: Add move operations with definitions in widget.cpp.
 *    But a move-only type like unique_ptr prevents the compiler form
 *    generating copy operations. Even if it did, the generated functions
 *    would copy only the unique_ptr (shallow copy), and we want to copy
 *    what the pointer points to (deep copy).
 * 4. Solution: Add copy operations with definitions in widget.cpp.
 */

#include "widget.h"


int main()
{
    Widget w1;

    auto w2(std::move(w1));  // move-construct w2

    w1 = std::move(w2);   // move-assign w1

    auto w3(w1);  // copy-construct w3

    w1 = w3;   // copy-assign w1

    return 0;
}

widget.h

/*
 * widget.h
 */

#include <memory>


class Widget
{
public:
    Widget();
    ~Widget();

    // Move operations
    Widget(Widget&& rhs);
    Widget& operator=(Widget&& rhs);

    // Copy operations
    Widget(const Widget& rhs);
    Widget& operator=(const Widget& rhs);

private:
    struct Impl;  // declare incomplete type
    std::unique_ptr<Impl> pImpl;  // unique_ptr supports incomplete types
};

widget.cpp

/*
 * widget.cpp
 */

#include "widget.h"
#include <string>
#include <vector>


struct Widget::Impl
{
    std::string name;
    std::vector<double> data;
};


Widget::Widget()
    : pImpl(std::make_unique<Impl>())
{}


// The compiler-generated destructor does the right thing,
// but its definition must be generated in the implementation
// file where Widget::Impl is a complete type.
Widget::~Widget() = default;


// Like destructor: compiler-generated in the implementation file.
Widget::Widget(Widget&& rhs) = default;
Widget& Widget::operator=(Widget&& rhs) = default;


Widget::Widget(const Widget& rhs)
    : pImpl(nullptr)
{
    if (rhs.pImpl) {
        pImpl = std::make_unique<Impl>(*rhs.pImpl);
    }
}


Widget& Widget::operator=(const Widget& rhs)
{
    if (!rhs.pImpl) {
        pImpl.reset();
    }
    else if (!pImpl) {
        pImpl = std::make_unique<Impl>(*rhs.pImpl);
    }
    else {
        *pImpl = *rhs.pImpl;
    }
    return *this;
}