使用 shared_ptr 处理不完整类型的 Pimpl 习语
Pimpl idiom using shared_ptr working with incomplete types
我正在阅读 Scott Meyers 的 Effective Modern C++,他正在讨论 pimpl 习语的使用,并指出 class 与 unique_ptr
的实现,但存在特殊成员函数的问题(例如析构函数)要求类型完整。这是因为 unique_ptr
的默认删除器在使用 delete p
之前静态断言要删除的类型是否完整。因此 class 的任何特殊成员函数都必须在实现文件中定义(而不是编译器生成的), 在 实现 class 已被定义之后。
章末他提到如果使用的智能指针是shared_ptr
,就不需要在实现文件中定义特殊的成员函数,这源于它支持自定义删除器的方式.引用:
The difference in behavior between std::unique_ptr and std::shared_ptr for
pImpl pointers stems from the differing ways these smart pointers support custom
deleters. For std::unique_ptr, the type of the deleter is part of the type of the smart
pointer, and this makes it possible for compilers to generate smaller runtime data
structures and faster runtime code. A consequence of this greater efficiency is that
pointed-to types must be complete when compiler-generated special functions (e.g.,
destructors or move operations) are used. For std::shared_ptr, the type of the
deleter is not part of the type of the smart pointer. This necessitates larger runtime
data structures and somewhat slower code, but pointed-to types need not be complete
when compiler-generated special functions are employed.
尽管如此,我仍然不明白为什么 shared_ptr
在 class 不完整的情况下仍然可以工作。使用 shared_ptr
时没有编译器错误的唯一原因似乎是因为没有像 unique_ptr
那样的静态断言,并且由于缺少断言,可能会出现未定义的运行时行为。
我不知道 shared_ptr
的析构函数的实现,但是(通过阅读 C++ Primer)我得到的印象是它的工作方式类似于:
del ? del(p) : delete p;
其中 del
是指向自定义删除器的指针或函数对象。 Cppreference 还明确了 shared_ptr
没有自定义删除器的析构函数使用 delete p
3) Uses the delete-expression delete ptr
if T
is not an array type; .... Y must be a complete type. The delete expression must be well formed, have well-defined behavior and not throw any exceptions.
强调删除的类型必须是完整的。 pimpl 习语的一个最小示例:
//widget.h
#ifndef WIDGET
#define WIDGET
#include <memory>
class Widget{
public:
Widget();
private:
struct Impl;
std::shared_ptr<Impl> pImpl;
};
#endif // WIDGET
//widget.cpp
#include <string>
#include "Widget.h"
struct Widget::Impl{
std::string name;
};
Widget::Widget(): pImpl(new Impl) {}
//main.cpp
#include <iostream>
#include "Widget.h"
int main(){
Widget a;
}
当 main.cpp
中的 Widget a
被编译时,shared_ptr
的模板被实例化为类型 Widget
(在 main.cpp
内)并且可能是编译后的结果shared_ptr
的析构函数包含行 delete pImpl
的执行,因为我没有提供自定义删除器。然而此时,Impl
仍未定义,但执行了行 delete pImpl
。这肯定是未定义的行为?
那么,当使用带有 shared_ptr
的 pimpl 惯用语时,我不必在实现文件中定义特殊成员函数以避免未定义的行为,这是怎么回事?
共享指针的删除器创建于此:
Widget::Widget(): pImpl(new Impl) {}
直到那时,所有共享指针都相当于 std::funciton<void(Impl*)>
。
当您用 T*
构造 shared_ptr
时,它会写入删除器并将其存储在 std::function
等价物中。那时类型必须完整。
因此,在 Impl
完全定义之后,您必须定义的唯一函数是那些从某种 T*
创建 pImpl
的函数。
我正在阅读 Scott Meyers 的 Effective Modern C++,他正在讨论 pimpl 习语的使用,并指出 class 与 unique_ptr
的实现,但存在特殊成员函数的问题(例如析构函数)要求类型完整。这是因为 unique_ptr
的默认删除器在使用 delete p
之前静态断言要删除的类型是否完整。因此 class 的任何特殊成员函数都必须在实现文件中定义(而不是编译器生成的), 在 实现 class 已被定义之后。
章末他提到如果使用的智能指针是shared_ptr
,就不需要在实现文件中定义特殊的成员函数,这源于它支持自定义删除器的方式.引用:
The difference in behavior between std::unique_ptr and std::shared_ptr for pImpl pointers stems from the differing ways these smart pointers support custom deleters. For std::unique_ptr, the type of the deleter is part of the type of the smart pointer, and this makes it possible for compilers to generate smaller runtime data structures and faster runtime code. A consequence of this greater efficiency is that pointed-to types must be complete when compiler-generated special functions (e.g., destructors or move operations) are used. For std::shared_ptr, the type of the deleter is not part of the type of the smart pointer. This necessitates larger runtime data structures and somewhat slower code, but pointed-to types need not be complete when compiler-generated special functions are employed.
尽管如此,我仍然不明白为什么 shared_ptr
在 class 不完整的情况下仍然可以工作。使用 shared_ptr
时没有编译器错误的唯一原因似乎是因为没有像 unique_ptr
那样的静态断言,并且由于缺少断言,可能会出现未定义的运行时行为。
我不知道 shared_ptr
的析构函数的实现,但是(通过阅读 C++ Primer)我得到的印象是它的工作方式类似于:
del ? del(p) : delete p;
其中 del
是指向自定义删除器的指针或函数对象。 Cppreference 还明确了 shared_ptr
没有自定义删除器的析构函数使用 delete p
3) Uses the delete-expression
delete ptr
ifT
is not an array type; .... Y must be a complete type. The delete expression must be well formed, have well-defined behavior and not throw any exceptions.
强调删除的类型必须是完整的。 pimpl 习语的一个最小示例:
//widget.h
#ifndef WIDGET
#define WIDGET
#include <memory>
class Widget{
public:
Widget();
private:
struct Impl;
std::shared_ptr<Impl> pImpl;
};
#endif // WIDGET
//widget.cpp
#include <string>
#include "Widget.h"
struct Widget::Impl{
std::string name;
};
Widget::Widget(): pImpl(new Impl) {}
//main.cpp
#include <iostream>
#include "Widget.h"
int main(){
Widget a;
}
当 main.cpp
中的 Widget a
被编译时,shared_ptr
的模板被实例化为类型 Widget
(在 main.cpp
内)并且可能是编译后的结果shared_ptr
的析构函数包含行 delete pImpl
的执行,因为我没有提供自定义删除器。然而此时,Impl
仍未定义,但执行了行 delete pImpl
。这肯定是未定义的行为?
那么,当使用带有 shared_ptr
的 pimpl 惯用语时,我不必在实现文件中定义特殊成员函数以避免未定义的行为,这是怎么回事?
共享指针的删除器创建于此:
Widget::Widget(): pImpl(new Impl) {}
直到那时,所有共享指针都相当于 std::funciton<void(Impl*)>
。
当您用 T*
构造 shared_ptr
时,它会写入删除器并将其存储在 std::function
等价物中。那时类型必须完整。
因此,在 Impl
完全定义之后,您必须定义的唯一函数是那些从某种 T*
创建 pImpl
的函数。