use a custom deleter
The problem is that unique_ptr<T>
must call the destructor T::~T()
in its own destructor, its move assignment operator, and unique_ptr::reset()
member function (only). However, these must be called (implicitly or explicitly) in several PIMPL situations (already in the outer class's destructor and move assignment operator).
As already pointed out in another answer, one way to avoid that is to move all operations that require unique_ptr::~unique_ptr()
, unique_ptr::operator=(unique_ptr&&)
, and unique_ptr::reset()
into the source file where the pimpl helper class is actually defined.
However, this is rather inconvenient and defies the very point of the pimpl idoim to some degree. A much cleaner solution that avoids all that is to use a custom deleter and only move its definition into the source file where the pimple helper class lives. Here is a simple example:
// file.h
class foo
{
struct pimpl;
struct pimpl_deleter { void operator()(pimpl*) const; };
std::unique_ptr<pimpl,pimpl_deleter> m_pimpl;
public:
foo(some data);
foo(foo&&) = default; // no need to define this in file.cc
foo&operator=(foo&&) = default; // no need to define this in file.cc
//foo::~foo() auto-generated: no need to define this in file.cc
};
// file.cc
struct foo::pimpl
{
// lots of complicated code
};
void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }
Instead of a separate deleter class, you can also use a free function or static
member of foo
:
class foo {
struct pimpl;
static void delete_pimpl(pimpl*);
using deleter = void(&)(pimpl*);
std::unique_ptr<pimpl,deleter> m_pimpl{nullptr,delete_pimpl};
public:
foo(some data);
};
Although this requires m_pimpl
to hold the address of the deleter, requiring extra storage that is not needed in the first example.