Unique_ptr pimpl 的用法 - 即使声明了析构函数也无法编译
Unique_ptr usage for pimpl - doesn't compile even though destructor is declared
我正在尝试使用 unique_ptr 作为粉刺成语。所以我在 class 中声明了一个析构函数,因此 unique_ptr 删除不会在 impl class 未定义的地方被实例化,然后我在另一个文件中定义它。
这是我的布局:
wrapper.h:
#pragma once
#include <memory>
struct Wrapper
{
class Impl;
~Wrapper();
std::unique_ptr<Impl> _impl;
};
wrapper.cpp:
#include "wrapper.h"
class Wrapper::Impl {};
Wrapper::~Wrapper() = default;
这个文件编译得很好。
但是,在编译 main.cpp 时出现不完整的类型错误(请参阅下面的错误):
main.cpp:
#include "wrapper.h"
int main()
{
Wrapper w;
return 0;
}
但是,如果我在 main.cpp 的末尾添加来自 wrapper.cpp 的两行,它编译得很好。
我不明白两件事:
- 为什么首先会发生错误?我认为在 class 中声明析构函数会移动删除调用点?
- 为什么在 main.cpp 文件的末尾 添加代码 会影响此错误?我虽然“如果使用默认删除器,T 必须在代码 调用删除器的位置完成 ,这发生在析构函数中”(来自 cppreference)。
我错过了什么?
更新:
根据@AdrianMole 的建议,我在 class Wrapper 定义中添加了一个 ctor 声明,出于某种原因它修复了错误,即使错误(和 unique_ptr 规范)指的是析构函数。
已更新wrapper.h:
struct Wrapper
{
class Impl;
Wrapper();
~Wrapper();
std::unique_ptr<Impl> _impl;
};
所以我要添加一个问题:
- 为什么添加构造函数声明可以解决这个问题?
这些是我在使用 MSVC 时遇到的错误,但类似的错误发生在 clang 或 gcc 中(我试过在线编译器):
memory(2536,1): error C2027: use of undefined type 'Wrapper::Impl'
wrapper.h(7): message : see declaration of 'Wrapper::Impl'
memory(2535): message : while compiling class template member function 'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const'
with
[
_Ty=Wrapper::Impl
]
memory(2647): message : see reference to function template instantiation 'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const' being compiled
with
[
_Ty=Wrapper::Impl
]
memory(2574): message : see reference to class template instantiation 'std::default_deleteWrapper::Impl' being compiled
wrapper.h(9): message : see reference to class template instantiation 'std::unique_ptrWrapper::Impl,std::default_delete<Wrapper::Impl>' being compiled
memory(2536,25): error C2338: can't delete an incomplete type
memory(2537,1): warning C4150: deletion of pointer to incomplete type 'Wrapper::Impl'; no destructor called
wrapper.h(7): message : see declaration of 'Wrapper::Impl'
任何构造函数定义(包括隐式定义的默认构造函数)都可能调用 class 类型的所有成员对象的类型的析构函数。这样做的理由是,如果构造一个后来的成员抛出,则需要调用所有先前成员的析构函数。最后一个成员的情况下,这不是严格要求的,但标准没有例外。
例如,如果您的 class 是:
struct complete_type_with_throwing_constructor {
complete_type_with_throwing_constructor() { throw 0; }
};
struct Wrapper
{
class Impl;
~Wrapper();
std::unique_ptr<Impl> _impl;
complete_type_with_throwing_constructor x;
};
然后 Wrapper
的隐式默认构造函数将构造 _impl
然后在 x
的默认构造函数抛出后销毁它。
如果您显式声明默认构造函数,这就是为什么您的代码可以运行的原因。这会将定义移动到可以实例化 std::unique_ptr<Impl>
的析构函数的位置。
关于你的第二个问题,你是对的,在之后添加定义时代码应该仍然不起作用。只是编译器没有检测到,所以没有报错。
我正在尝试使用 unique_ptr 作为粉刺成语。所以我在 class 中声明了一个析构函数,因此 unique_ptr 删除不会在 impl class 未定义的地方被实例化,然后我在另一个文件中定义它。
这是我的布局:
wrapper.h:
#pragma once
#include <memory>
struct Wrapper
{
class Impl;
~Wrapper();
std::unique_ptr<Impl> _impl;
};
wrapper.cpp:
#include "wrapper.h"
class Wrapper::Impl {};
Wrapper::~Wrapper() = default;
这个文件编译得很好。 但是,在编译 main.cpp 时出现不完整的类型错误(请参阅下面的错误):
main.cpp:
#include "wrapper.h"
int main()
{
Wrapper w;
return 0;
}
但是,如果我在 main.cpp 的末尾添加来自 wrapper.cpp 的两行,它编译得很好。 我不明白两件事:
- 为什么首先会发生错误?我认为在 class 中声明析构函数会移动删除调用点?
- 为什么在 main.cpp 文件的末尾 添加代码 会影响此错误?我虽然“如果使用默认删除器,T 必须在代码 调用删除器的位置完成 ,这发生在析构函数中”(来自 cppreference)。
我错过了什么?
更新:
根据@AdrianMole 的建议,我在 class Wrapper 定义中添加了一个 ctor 声明,出于某种原因它修复了错误,即使错误(和 unique_ptr 规范)指的是析构函数。
已更新wrapper.h:
struct Wrapper
{
class Impl;
Wrapper();
~Wrapper();
std::unique_ptr<Impl> _impl;
};
所以我要添加一个问题:
- 为什么添加构造函数声明可以解决这个问题?
这些是我在使用 MSVC 时遇到的错误,但类似的错误发生在 clang 或 gcc 中(我试过在线编译器):
memory(2536,1): error C2027: use of undefined type 'Wrapper::Impl'
wrapper.h(7): message : see declaration of 'Wrapper::Impl'
memory(2535): message : while compiling class template member function 'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const'
with [ _Ty=Wrapper::Impl ]
memory(2647): message : see reference to function template instantiation 'void std::default_deleteWrapper::Impl::operator ()(_Ty *) noexcept const' being compiled
with [ _Ty=Wrapper::Impl ]
memory(2574): message : see reference to class template instantiation 'std::default_deleteWrapper::Impl' being compiled
wrapper.h(9): message : see reference to class template instantiation 'std::unique_ptrWrapper::Impl,std::default_delete<Wrapper::Impl>' being compiled
memory(2536,25): error C2338: can't delete an incomplete type
memory(2537,1): warning C4150: deletion of pointer to incomplete type 'Wrapper::Impl'; no destructor called
wrapper.h(7): message : see declaration of 'Wrapper::Impl'
任何构造函数定义(包括隐式定义的默认构造函数)都可能调用 class 类型的所有成员对象的类型的析构函数。这样做的理由是,如果构造一个后来的成员抛出,则需要调用所有先前成员的析构函数。最后一个成员的情况下,这不是严格要求的,但标准没有例外。
例如,如果您的 class 是:
struct complete_type_with_throwing_constructor {
complete_type_with_throwing_constructor() { throw 0; }
};
struct Wrapper
{
class Impl;
~Wrapper();
std::unique_ptr<Impl> _impl;
complete_type_with_throwing_constructor x;
};
然后 Wrapper
的隐式默认构造函数将构造 _impl
然后在 x
的默认构造函数抛出后销毁它。
如果您显式声明默认构造函数,这就是为什么您的代码可以运行的原因。这会将定义移动到可以实例化 std::unique_ptr<Impl>
的析构函数的位置。
关于你的第二个问题,你是对的,在之后添加定义时代码应该仍然不起作用。只是编译器没有检测到,所以没有报错。