I'm reviewing a non-compiling code where I find a design similar to this:
B.h
#include <memory>
class A;
class B {
private:
int val;
// pImpl idiom
std::unique_ptr<A> pImpl;
constexpr B(int x): val(x){};
virtual ~B();
};
destructor is defined in B.cpp
, yet the constructor being constexpr
it implies that it is defined within B.h
.
But then compiling is failing because the compiler needs to have a constructor for A
, which, at this point is an incomplete type.
Yet I think that, here, constexpr
is a design error as I can't see how a B
can be constructed at compile-time with an implementation.
Thus, is constexpr
erroneous in this context or is there a way to construct a B
at compile time (I don't think that std::unique_ptr
can be constructed at compile-time except from nullptr
)?
NB I tried to push the constructor definition inside B.cpp
but the linker then (logically I think) triggered undefined reference on the constructor...
NB compilation has been tested only on msvc so far
NB I read a bunch of posts about pimpl and unique_ptr
(which are numerous) but I might have missed an adequate one and the question is very possibly duplicate...
It's not an issue with constexpr
. When you construct members of a class, it needs the destructors of each member to be available, because if a succeeding member's constructor throws or the constructor body throws, the destructor will need to be called.
So you can't use default_delete<A>
because the class is not complete. This is why the constructor is usually implemented in the source file with pimpl. The easiest fix is to use raw pointer A* pImpl
and remember to delete it in the destructor. Or use a different deleter:
class B {
private:
static void delete_a(A* p) noexcept;
struct a_deleter {
void operator()(A* p) const noexcept { delete_a(p); }
};
int val;
// pImpl idiom
std::unique_ptr<A, a_deleter> pImpl;
constexpr B(int x): val(x){};
virtual ~B() = default;
};
// Source/implementation file
class A {
// ...
};
void B::delete_a(A* p) noexcept {
delete p;
}