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;
}