我正在阅读Scott Meyers撰写的Effective Modern C ++,他正在讨论使用pimpl习语并用unique_ptr
指向实现类,但是存在特殊成员函数的问题(例如析构函数)要求类型完整。这是因为unique_ptr
的默认删除器在使用delete p
之前静态断言要删除的类型是否完整。因此,必须在实现文件中定义类的任何特殊成员函数(而不是编译器生成的),在定义实现类之后。
在本章的最后,他提到如果使用的智能指针是shared_ptr
,则不需要在实现文件中定义特殊的成员函数,这源于它支持自定义删除器的方式。引用:
std :: unique_ptr和std :: shared_ptr之间的行为差异 pImpl指针源于这些智能指针支持自定义的不同方式 删除者。对于std :: unique_ptr,删除器的类型是智能类型的一部分 指针,这使编译器可以生成较小的运行时数据 结构和更快的运行时代码。这种更高效率的结果是 当编译器生成的特殊函数时,指向的类型必须完整(例如, 使用析构函数或移动操作。对于std :: shared_ptr,类型 deleter不是智能指针类型的一部分。这需要更大的运行时间 数据结构和稍慢的代码,但指向的类型不需要完整 当使用编译器生成的特殊函数时。
尽管如此,我仍然无法理解为什么shared_ptr
在没有完成课程的情况下仍能正常工作。在使用shared_ptr
时,似乎没有编译器错误的唯一原因是因为没有像unique_ptr
这样的静态断言,并且由于缺少断言而可能会发生未定义的运行时行为。
我不知道shared_ptr
的析构函数的实现,但是(从阅读C ++ Primer)我收集了它的工作原理:
del ? del(p) : delete p;
其中del
是自定义删除器的指针或函数对象。 Cppreference还明确说明没有自定义删除的shared_ptr
析构函数使用delete p
3)如果
delete ptr
不是数组类型,则使用delete-expressionT
; .... Y必须是完整类型。删除表达式必须格式良好,具有明确定义的行为,并且不会抛出任何异常。
强调删除类型必须完整的事实。 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;
}
编译Widget a
中的main.cpp
时,shared_ptr
的模板会立即显示Widget
类型(main.cpp
内),并且可能是由此产生的编译析构函数shared_ptr
包含行delete pImpl
的执行,因为我没有提供自定义deletor。但是,此时Impl
仍未定义,但执行了行delete pImpl
。这肯定是未定义的行为?
那么当使用pimpl惯用语shared_ptr
时,我怎么不必在实现文件中定义特殊成员函数以避免未定义的行为?
答案 0 :(得分:7)
此处创建共享指针的删除器:
Widget::Widget(): pImpl(new Impl) {}
直到那时,所有共享指针都相当于std::funciton<void(Impl*)>
。
当您使用shared_ptr
构建T*
时,它会写一个删除器并将其存储在std::function
等效项中。此时类型必须完整。
因此,Impl
完全定义后,您必须定义的唯一功能是那些从某种pImpl
创建T*
的功能。