3

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:

  • I'm trying to expose A and C to consumers of my library and keep B internal.
  • There is no B.cpp here, it would be empy.
Patrick Quirk
  • 23,334
  • 2
  • 57
  • 88

2 Answers2

3

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).

AnatolyS
  • 4,249
  • 18
  • 28
1

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).

SirGuy
  • 10,660
  • 2
  • 36
  • 66