使用 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?
我(还)没有完全理解这个问题,但原因是 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.
我一直在尝试使用 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?
我(还)没有完全理解这个问题,但原因是 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.