Private implementation using smart pointers and deletion trouble

It is often desirable to hide excess implementation detail from users of libraries by using forward declarations and private pointers. There is a lot written on the Internet on this idiom. If you are using it you may very well want to use the boost::scoped_ptr<> smart pointer. There is however one simple trap you can fall into doing this and then you may get error messages related to incomplete type such as this:

/usr/include/boost/checked_delete.hpp: In function ‘void boost::checked_delete(T*) [with T = B]’:
/usr/include/boost/scoped_ptr.hpp:77:   instantiated from ‘boost::scoped_ptr<T>::~scoped_ptr() [with T = B]’
pimpl_a.hpp:8:   instantiated from here
/usr/include/boost/checked_delete.hpp:32: error: invalid application of ‘sizeof’ to incomplete type ‘B’
/usr/include/boost/checked_delete.hpp:32: error: creating array with negative size (‘-0x00000000000000001’)
/usr/include/boost/checked_delete.hpp:33: error: invalid application of ‘sizeof’ to incomplete type ‘B’
/usr/include/boost/checked_delete.hpp:33: error: creating array with negative size (‘-0x00000000000000001’)
/usr/include/boost/checked_delete.hpp:34: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

Here is a complete example of how this can happen and how to fix.

The problem

Lets assume the class we wish to use is declared in file pimpl_a.hpp:

// Bojan Nikolic <bojan@bnikolic.co.uk>
// File pimpl_a.hpp

#include <boost/scoped_ptr.hpp>

// Forward declaration
class B;

class A {
  boost::scoped_ptr<B> _imp;
public:
  int get3(void);
};

The implementation is in file pimpl_a.cpp, including the hidden worker class B:

// Bojan Nikolic <bojan@bnikolic.co.uk>
// File pimpl_a.cpp

#include "pimpl_a.hpp"

class B
{
public:
  int get3(void)
  {
    return 3;
  }
};

int A::get3(void)
{
  return _imp->get3();
}

We would expect to be able to use the class A by including the header file pimpl_a.hpp, for as example like this:

// Bojan Nikolic <bojan@bnikolic.co.uk>
// File pimpl_use.cpp

#include "pimpl_a.hpp"

int main(void)
{
  A  a;
}

But this however leads to the incomplete type errors as shown above. Note that if you use the boost::shared_ptr<> these errors will not occur. However, this smart pointer type has semantics and efficiency so is not a straight forward replacement for boost::scoped_ptr<>.

The reason

The reason for this problem and for the use of the checked_delete concept are described in the documentation of the boost header <boost/checked_delete.hpp>.

The solution

The solution is very simple: you need to define the constructors of the class A in a translation unit in which the type B is fully defined. For example this will solve the above errors:

// Bojan Nikolic <bojan@bnikolic.co.uk>
// File pimpl_a.hpp

#include <boost/scoped_ptr.hpp>

// Forward declaration
class B;

class A {
  boost::scoped_ptr<B> _imp;
public:
  A();
  virtual ~A();
  int get3(void);
};

and this:

// Bojan Nikolic <bojan@bnikolic.co.uk>
// File pimpl_a.cpp

#include "pimpl_a.hpp"

class B
{
public:
  int get3(void)
  {
    return 3;
  }
};

A::~A(){}

A::A(){}

int A::get3(void)
{
  return _imp->get3();
}

Summary

If you are using boost::scoped_ptr<> within classes and on incomplete types, ensure the class constructor and destructor are defined in translation unit which contains the complete type that your are pointing to.

More generally, note that deletion of incomplete types in C++ is an undefined operation, but not necessarily an error condition.