I've been trying to implement the PIMPL idiom by using a unique_ptr. I inspired myself from several articles that always highlight the same important point : ONLY DECLARE the destructor in the header of the class implementing PIMPL and then define it in your .cpp file. Otherwise, you'll get compilation error like "Incomplete type bla bla".
Alright, I did it on a little test which respects this, but I still have the "incomplete type" error. The code is just below, it's very short.
A.hpp:
#pragma once
#include <memory>
class A
{
public:
A();
~A();
private:
class B;
std::unique_ptr<B> m_b = nullptr;
};
A.cpp:
#include "A.hpp"
class A::B
{
};
A::A()
{
}
A::~A() // could be also '= default'
{
}
main.cpp:
#include "A.hpp"
int main()
{
A a1;
return 0;
}
I built in 2 (quick and dirty) ways and the result is very surprising from my point of view.
First I built without linking A.cpp
g -c A.cpp
No error so far.
Then, I compiled A.cpp and main.cpp to create an executable
g A.cpp main.cpp -o test
That's where I am into troubles. Here I got the famous error about the incomplete type:
In file included from /usr/include/c /9/memory:80,
from A.hpp:2,
from test.cpp:2:
/usr/include/c /9/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = A::B]’:
/usr/include/c /9/bits/unique_ptr.h:292:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = A::B; _Dp = std::default_delete<A::B>]’
A.hpp:11:28: required from here
/usr/include/c /9/bits/unique_ptr.h:79:16: error: invalid application of ‘sizeof’ to incomplete type ‘A::B’
79 | static_assert(sizeof(_Tp)>0,
| ^~~~~~~~~~~
I know what the constraints are when you intend to use unique_ptr as a part the PIMPL idiom and I tried to care about them. However, in this case, I have to admit that I'm out of idea (and hairs as it gets me on my nerves).
Do I do something wrong, or are we just not censed to use unique_ptr in such a case ?
CodePudding user response:
I don't (yet) fully understand the issue, but the cause is the default member initializer of the m_ptr
member. It compiles wihout errors if you use the member initializer list instead:
// A.hpp:
class A
{
public:
A();
~A();
private:
class B;
std::unique_ptr<B> m_b; // no initializer here
};
// A.cpp:
A::A() : m_b(nullptr) // initializer here
{
}
https://wandbox.org/permlink/R6SXqov0nl7okAW0
Note that clangs error message is better at pointing at the line that causes the error:
In file included from prog.cc:1:
In file included from ./A.hpp:3:
In file included from /opt/wandbox/clang-13.0.0/include/c /v1/memory:682:
In file included from /opt/wandbox/clang-13.0.0/include/c /v1/__memory/shared_ptr.h:25:
/opt/wandbox/clang-13.0.0/include/c /v1/__memory/unique_ptr.h:53:19: error: invalid application of 'sizeof' to an incomplete type 'A::B'
static_assert(sizeof(_Tp) > 0,
^~~~~~~~~~~
/opt/wandbox/clang-13.0.0/include/c /v1/__memory/unique_ptr.h:318:7: note: in instantiation of member function 'std::default_delete<A::B>::operator()' requested here
__ptr_.second()(__tmp);
^
/opt/wandbox/clang-13.0.0/include/c /v1/__memory/unique_ptr.h:272:19: note: in instantiation of member function 'std::unique_ptr<A::B>::reset' requested here
~unique_ptr() { reset(); }
^
./A.hpp:12:28: note: in instantiation of member function 'std::unique_ptr<A::B>::~unique_ptr' requested here
std::unique_ptr<B> m_b = nullptr;
^
./A.hpp:11:9: note: forward declaration of 'A::B'
class B;
^
1 error generated.