Pimpl习惯使用shared_ptr处理不完整类型

时间:2016-11-15 21:28:42

标签: c++ c++11 smart-pointers pimpl-idiom

我正在阅读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-expression T; .... 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时,我怎么不必在实现文件中定义特殊成员函数以避免未定义的行为?

1 个答案:

答案 0 :(得分:7)

此处创建共享指针的删除器:

Widget::Widget(): pImpl(new Impl) {}

直到那时,所有共享指针都相当于std::funciton<void(Impl*)>

当您使用shared_ptr构建T*时,它会写一个删除器并将其存储在std::function等效项中。此时类型必须完整。

因此,Impl完全定义后,您必须定义的唯一功能是那些从某种pImpl创建T*的功能。