Pimpl what you can, forward-declare what you can't (i.e. things in function signatures).
I don't bother Pimpling every class, but some of the more commonly used and larger classes get Pimpl'd.
I Pimpl like this:
MyClass.h#include "Common/Assorted/Pimpl.h"
class MyClass
{
public:
//...stuff...
private:
private:
struct Impl;
Pimpl<Impl> pImpl;
};
MyClass.cpp#include "Common/Assorted/Pimpl_Impl.h"
struct MyClass::Impl
{
Impl(const std::string &str);
std::string stuff;
int blah = 357;
};
//A constructor for the impl is completely optional.
MyClass::Impl::Impl(const std::string &str)
{
stuff = str;
}
MyClass::MyClass() : pImpl("Test")
{
std::cout << pImpl->stuff << std::endl;
}
Pimpl.h#ifndef COMMON_ASSORTED_PIMPL_H
#define COMMON_ASSORTED_PIMPL_H
/*
This file provides the declaration of the Pimpl class,
and should only be #included by the .h file of the class being Pimpl'd.
The Pimpl_Impl.h class should be #included by the .cpp file of the class being Pimpl'd.
*/
template<typename Type>
class Pimpl
{
public:
//Default constructor.
Pimpl();
//Multi-argument constructor.
template<typename ...Args>
Pimpl(Args &&...);
//Copy constructor.
Pimpl(const Pimpl &other);
//Copy-assignment operator.
Pimpl &operator=(const Pimpl&);
//Move constructor.
Pimpl(Pimpl &&other);
//Move-assignment operator.
Pimpl &operator=(Pimpl&&);
//Destructor.
~Pimpl();
//Non-const access:
Type *operator->();
Type *operator*();
//Const access:
const Type *operator->() const;
const Type *operator*() const;
private:
//The pointer to the implementation of the class being Pimpl'd.
Type *data = nullptr;
};
#endif // COMMON_ASSORTED_PIMPL_H
Pimpl_Impl.h#ifndef COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H
#define COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H
/*
This file provides the implementation of the Pimpl class,
and should only be #included by the .cpp file of the class being Pimpl'd.
*/
#include "Common/Assorted/Pimpl.h"
#include <utility>
//Default constructor.
template<typename Type>
Pimpl<Type>::Pimpl()
{
this->data = new Type;
}
//Multi-argument constructor.
template<typename Type>
template<typename ...Args>
Pimpl<Type>::Pimpl(Args && ...args)
{
this->data = new Type(std::forward<Args>(args)...);
}
//Copy constructor.
template<typename Type>
Pimpl<Type>::Pimpl(const Pimpl &other)
{
this->data = new Type(*(other.data));
}
//Copy-assignment operator.
template<typename Type>
Pimpl<Type> &Pimpl<Type>::operator=(const Pimpl &other)
{
if(this != &other)
{
Type *temp = new Type(*(other.data));
std::swap(this->data, temp);
delete temp;
}
return *this;
}
//Move constructor.
template<typename Type>
Pimpl<Type>::Pimpl(Pimpl &&other)
{
//Swap the pointers so this class gets the old data pointer, and 'other' gets the null pointer.
std::swap(this->data, other.data);
}
//Move-assignment operator.
template<typename Type>
Pimpl<Type> &Pimpl<Type>::operator=(Pimpl &&other)
{
//Swap the pointers so this class gets the old data pointer, and 'other' gets the null pointer.
std::swap(this->data, other.data);
return *this;
}
//Destructor.
template<typename Type>
Pimpl<Type>::~Pimpl()
{
delete this->data;
}
//Non-const access:
template<typename Type>
Type *Pimpl<Type>::operator->()
{
return this->data;
}
template<typename Type>
Type *Pimpl<Type>::operator*()
{
return this->data;
}
//Const access:
template<typename Type>
const Type *Pimpl<Type>::operator->() const
{
return this->data;
}
template<typename Type>
const Type *Pimpl<Type>::operator*() const
{
return this->data;
}
#endif //COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H
The 'Pimpl' class is a reusable and consistent tool to Pimpl my classes. It manages the data pointer for you, so RAII, but at the same time is compact and small enough to save the (presumed, on my part) compile overhead of #including <memory> for unique_ptr, which (in GCC) includes a bunch of other headers that include other headers.