std::unique_ptr<incomplete_type> without custom deleter

127 views Asked by At

The following https://godbolt.org/z/5qd8WbYz9 doesn't work and after reading the duplicate I thought I would have to define an custom deleter

However, I managed to get it working https://godbolt.org/z/dzYvsdKP1 by

  • adding a default constructor implemented in BB.cpp (to avoid implicit in-class default constructor)
  • removing the {nullptr} in-class initialization of the unique_ptr

Can someone explain why the {nullptr} make's it break?

It's important to note that std::default_deleter<AA> can be instantiated even when AA is incomplete.

//BB.h
#include <memory>
struct AA;
struct BB
{
    
    // B(); <- needed
    ~BB();
    std::unique_ptr<AA> u{ nullptr }; // need to remove {nullptr} initializer
};
// BB.cpp
#include "BB.h"
struct AA {};
BB::~BB() = default;
// BB::BB() = default; <- needed
// main.cpp
#include "BB.h"
int main()
{
    BB bb{};
}
2

There are 2 answers

8
Ted Lyngmo On BEST ANSWER

AA can not be an incomplete type at the time BB::~BB is defined since AA must be a complete type at the time std::default_delete::operator() is called (which will happen in BB::~BB. Otherwise, the program is ill-formed.

cppreference:

The reason for this requirement is that calling delete on an incomplete type is undefined behavior in C++ if the complete class type has a nontrivial destructor or a deallocation function, as the compiler has no way of knowing whether such functions exist and must be invoked.

The situation is the same as when you are implementing something using the pimpl idiom.

  • AA can be incomplete when defining BB.
  • AA must be complete when defining BB::~BB.
  • Similarly, leaving BB::BB implicitly defined or = defaulted triggers a static_assert by default_delete in g++ and clang++ since it would then need to define unique_ptr<AA, default_delete<AA>>s destructor too early:
    ~unique_ptr() { default_delete<AA>{}(the_pointer); } // oups, sizeof(AA) unknown
    
  • Dererring the definitions of BB member functions until AA is defined is the idiomatic solution.
0
Tom Huntington On

So pimpl with assignment initialization doesn't compile for any venders anymore (although it used to on clang and msvc). But with brace initialization it's only broken on msvc (not conformant I guess)

https://godbolt.org/z/KchervEY3

#include <memory>
struct incomplete_type;
struct A
{
    A();
    ~A();
    std::unique_ptr<incomplete_type> u1; // always fine
    std::unique_ptr<incomplete_type> u2{ nullptr }; // error in msvc only
    //std::unique_ptr<incomplete_type> u3 = nullptr; // always error
};
int main()
{
    A bb{};
}

I'm still not sure exactly why assignment initialization shoudln't compile but it was discussed below that P0859r0 brought it about

https://developercommunity.visualstudio.com/t/vs2022-173-Preview-std::unique_ptr-need/10107855#T-N10306156