2

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

Oersted
  • 769
  • 16
  • 2
    " (I don't think that std::unique_ptr can be constructed at compile-time except from nullptr" and thats what happens here. There is no initializer for `pImpl` hence the default constructor is called, which is indeed `constexpr`: https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr – 463035818_is_not_an_ai Jun 14 '23 at 12:10
  • @463035818_is_not_a_number but in this case, why does the compiler is complaining that `A` is incomplete? Actually, he is trying to implement destructor for the ```std::unique_ptr``` though, in this constructor, it can be only a ```std::unique_ptr``` wrapping ```nullptr```? – Oersted Jun 14 '23 at 12:15
  • One possibility is to create an interface class that `A` will inherit from. There is an example here, but the question is not a duplicate: https://stackoverflow.com/questions/24635255/is-it-possible-to-write-an-agile-pimpl-in-c/24638702#24638702 – Galik Jun 14 '23 at 15:08
  • @Galik, though I validate Artyer answer, your proposal seems more natural. I include the interface, no forward declaration. Not sure, though, that it is possible in the actual code as I suspect also a circular reference... But from my sole wording above, your proposal is absolutely valid. – Oersted Jun 14 '23 at 15:42

1 Answers1

7

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;
}
Artyer
  • 31,034
  • 3
  • 47
  • 75