使用 std::unique_ptr PIMPL 习语真的有效吗?

Does PIMPL idiom actually work using std::unique_ptr?

我一直在尝试使用 unique_ptr 来实现 PIMPL 习语。 我从几篇总是强调相同重点的文章中获得启发:仅在 class 实现 PIMPL 的 header 中声明析构函数,然后在您的 .cpp 文件中定义它。否则,你会得到类似“Incomplete type bla bla”的编译错误。

好吧,我做了一个尊重这一点的小测试,但我仍然有“不完整类型”错误。代码就在下面,很短。

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;
}

我用 2 种(快速而肮脏的)方法构建,从我的角度来看,结果非常令人惊讶。

首先我在没有链接的情况下构建 A.cpp

g++ -c A.cpp

到目前为止没有错误。

然后,我编译 A.cpp 和 main.cpp 来创建一个可执行文件

g++ A.cpp main.cpp -o test

这就是我遇到麻烦的地方。在这里我得到了关于不完整类型的著名错误:

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,
      |                ^~~~~~~~~~~

我知道当您打算使用 unique_ptr 作为 PIMPL 习语的一部分时有什么限制,我试图关心它们。但是,在这种情况下,我不得不承认我没有想法(而且头发让我很紧张)。

是我做错了什么,还是在这种情况下我们不被禁止使用 unique_ptr?

Live demo

我(还)没有完全理解这个问题,但原因是 m_ptr 成员的默认成员初始值设定项。如果您改用成员初始值设定项列表,它会编译而不会出错:

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

请注意,clangs 错误消息最好指向导致错误的行:

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.