Reputation: 23747
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:
- T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
- T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
- T is a union and has a variant member with non-trivial destructor.
- 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:
A
and C
to consumers of my library and keep B
internal.Upvotes: 3
Views: 1566
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
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