q0987 q0987 - 2 months ago 7
C++ Question

Must provide destructor in the PIMPL

// main_pimpl_sample.cpp
#include "pimpl_sample.hpp"

using namespace std;

int main()
{
pimpl_sample p;

return 0;
}

// pimpl_sample.cpp

#include "pimpl_sample.hpp"

struct pimpl_sample::impl {
};

pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
}

// pimpl_sample::~pimpl_sample()
// cause problem if missed
// {}


// pimpl_sample.hpp

#if !defined (PIMPL_SAMPLE)
#define PIMPL_SAMPLE

#include <boost/scoped_ptr.hpp>

class pimpl_sample {
struct impl;
boost::scoped_ptr<impl> pimpl_;

public:
pimpl_sample();
//~pimpl_sample(); cause problem if missed
void do_something();
};

#endif


~/Documents/C++/boost $ g++ --version
g++ (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2

~/Documents/C++/boost $ g++ -o main_pimpl_sample main_pimpl_sample.cpp pimpl_sample.cpp pimpl_sample.hpp
In file included from /usr/include/boost/smart_ptr/scoped_ptr.hpp:15:0,
from /usr/include/boost/scoped_ptr.hpp:14,
from pimpl_sample.hpp:6,
from main_pimpl_sample.cpp:2:
/usr/include/boost/checked_delete.hpp: In function ‘void boost::checked_delete(T*) [with T = pimpl_sample::impl]’:
/usr/include/boost/smart_ptr/scoped_ptr.hpp:80:9: instantiated from ‘boost::scoped_ptr<T>::~scoped_ptr() [with T = pimpl_sample::impl]’
pimpl_sample.hpp:8:20: instantiated from here
/usr/include/boost/checked_delete.hpp:32:58: error: invalid application of ‘sizeof’ to incomplete type ‘pimpl_sample::impl’
/usr/include/boost/checked_delete.hpp:32:58: error: creating array with negative size (‘-0x00000000000000001’)


The solution to above compilation error is to manually provide a destructor. The indicated reason is as follows:


you must still remember to define the destructor manually; the reason
is that at the time the compiler generates an implicit destructor, the
type impl is incomplete, so its destructor isn't called.


Question> I still have difficulties to absorb the above idea and would like to know a little detail why we have to provide a manual destructor here.

Thank you

Kos Kos
Answer

TL;DR Declare an explicit destructor and implement it in a code module (not in the header file).

If you don't create the destructor, then the compiler creates an empty automatic destructor in every translation unit which tries to destroy an object of this class. You'd get equivalent behaviour if you defined an empty inline destructor in the class header.

This causes an error because the destructor is also responsible for calling destructors of all the class' fields, which - in order - needs the instantiation of the method template boost::scoped_ptr<impl>::~scoped_ptr();. This template, in turn, cannot be instantiated then because it tries to delete an object of type impl, which is only forward declared in that scope (and you need a full definition to know how to delete this object.


OTOH, if you declare the non-inline constructor in the header, its code is only generated in pimpl_sample.cpp, where also lies the definition of impl, so the scoped_ptr's destructor can be instantiated successfully.

Other translation units then only call pimpl_sample's destructor as an external method, so they don't need to generate it and instantiate scoped_ptr's destructor on their own.