Patrick Quirk
Patrick Quirk

Reputation: 23747

Why is this type incomplete when using the PIMPL idiom?

I'm using the PIMPL idiom, and specifically I'm using the template provided from this post. Given the set of classes below and compiling with VS2015 Update 3, I'm getting compile errors:

Error C2027 use of undefined type 'C::C_impl' (compiling source file src\A.cpp)

Error C2338 can't delete an incomplete type (compiling source file src\A.cpp)

Warning C4150 deletion of pointer to incomplete type 'C::C_impl'; no destructor called (compiling source file src\A.cpp)

I can resolve this by uncommenting C::~C(), which leads me to believe that something is preventing ~C() from being automatically generated but I don't understand what. According to this reference, the destructor for type T is implicitly defined as deleted if any of the following is true:

  1. T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
  2. T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
  3. T is a union and has a variant member with non-trivial destructor.
  4. The implicitly-declared destructor is virtual (because the base class has a virtual destructor) and the lookup for the deallocation function (operator delete() results in a call to ambiguous, deleted, or inaccessible function.

Items #2, 3, and 4 obviously do not apply to C, and I don't believe that #1 applies because pimpl<> (C's only member) defines a destructor explicitly.

Can someone please explain what's going on?

A.h

#pragma once
#include <Pimpl.h>

class A
{
private:
    struct A_impl;
    pimpl<A_impl> m_pimpl;
};

B.h

#pragma once
#include "C.h"

class B
{
private:
    C m_C;
};

C.h

#pragma once
#include <Pimpl.h>

class C
{
public:
    // Needed for the PIMPL pattern
    //~C();

private:
    struct C_impl;
    pimpl<C_impl> m_pimpl;
};

A.cpp

#include <memory>
#include "A.h"
#include "B.h"
#include <PimplImpl.h>

struct A::A_impl
{
    std::unique_ptr<B> m_pB;
};

// Ensure all the code for the template is compiled
template class pimpl<A::A_impl>;

C.cpp

#include <C.h>
#include <PimplImpl.h>

struct C::C_impl { };

// Needed for the PIMPL pattern
//C::~C() = default;

// Ensure all the code for the template is compiled
template class pimpl<C::C_impl>;

For completeness, the PIMPL implementation from the post referenced above:

pimpl.h

#pragma once
#include <memory>

template<typename T>
class pimpl
{
private:
    std::unique_ptr<T> m;
public:
    pimpl();
    template<typename ...Args> pimpl(Args&& ...);
    ~pimpl();
    T* operator->();
    T& operator*();
};

PimplImpl.h

#pragma once
#include <utility>

template<typename T>
pimpl<T>::pimpl() : m{ new T{} } {}

template<typename T>
template<typename ...Args>
pimpl<T>::pimpl(Args&& ...args)
    : m{ new T{ std::forward<Args>(args)... } }
{
}

template<typename T>
pimpl<T>::~pimpl() {}

template<typename T>
T* pimpl<T>::operator->() { return m.get(); }

template<typename T>
T& pimpl<T>::operator*() { return *m.get(); }

A few notes about the code above:

Upvotes: 3

Views: 1566

Answers (2)

SirGuy
SirGuy

Reputation: 10770

I believe the problem is that std::unique_ptr requires knowing the destruction function (ie, the destructor) at time of instantiation.

With the default destructor ~C() the compiler generates it at point of usage, which means that it is trying to delete the pimpl<C_impl> object with the information available to it in A.cpp. Of course, since C_impl is only declared at that point the compiler does not know how to destroy a C_impl object and that is the error you receive.

Uncommenting ~C(); tells the compiler not to worry about how to destroy a C_impl, that will be defined somewhere else. In your case that is defined in C.cpp where the definition of C_impl is known.

In response to your edit, the destructor for pimpl<C_impl> has std::unique_ptr<C_impl> which has a non-static data member with inaccessible destructor (C_impl is an incomplete type at point of usage withing A.cpp).

Upvotes: 1

AnatolyS
AnatolyS

Reputation: 4319

You have to define the C destructor manually after definition C::C_impl because for default compiler tries to generate C destructor in point of usage, but it can be point where C::C_impl definition can not be found (for example, it can be B.cpp).

Upvotes: 3

Related Questions