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

Ser*_*uin 5 c++ pimpl-idiom smart-pointers c++11

我正在阅读Scott Meyers撰写的Effective Modern C++,他正在讨论使用pimpl习语并指向实现类unique_ptr,但是存在需要完整类型的特殊成员函数(例如析构函数)的问题.这是因为unique_ptrdelete p使用之前,默认删除器静态断言要删除的类型是否完整.所以任何类特殊的成员函数必须在实现文件中定义(而不是编译器生成的),之后实现类已定义.

在本章的最后,他提到如果使用的是智能指针,则不需要在实现文件中定义特殊的成员函数shared_ptr,这源于它支持自定义删除器的方式.报价:

pImpl指针的std :: unique_ptr和std :: shared_ptr之间的行为差​​异源于这些智能指针支持自定义删除器的不同方式.对于std :: unique_ptr,删除器的类型是智能指针类型的一部分,这使编译器可以生成更小的运行时数据结构和更快的运行时代码.这种更高效率的结果是,当使用编译器生成的特殊函数(例如,析构函数或移动操作)时,指向类型必须是完整的.对于std :: shared_ptr,删除器的类型不是智能指针类型的一部分.这需要更大的运行时数据结构和稍慢的代码,但是当使用编译器生成的特殊函数时,指向的类型不需要完整.

尽管如此,我仍然不明白为什么shared_ptr没有完成课程仍然可以工作.这似乎是使用时没有编译器错误的唯一原因shared_ptr是因为没有像has那样的静态断言unique_ptr,并且由于缺少断言而可能会发生未定义的运行时行为.

我不知道shared_ptr析构函数的实现,但是(从阅读C++ Primer)我收集的印象它的工作原理如下:

del ? del(p) : delete p;
Run Code Online (Sandbox Code Playgroud)

del自定义删除器的指针或函数对象在哪里.Cppreference还清楚地说明shared_ptr了没有自定义删除器使用的析构函数delete p

3)delete ptr如果T不是数组类型,则使用delete-expression ; .... 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;
}
Run Code Online (Sandbox Code Playgroud)

Widget ain main.cpp编译时,模板shared_ptr是为类型Widget(内部main.cpp)即时输出的,并且可能是由此产生的编译析构函数shared_ptr包含行的执行delete pImpl,因为我没有提供自定义deletor.但是,此时Impl仍未定义,但该行delete pImpl已执行.这肯定是未定义的行为?

那么在使用pimpl习语时怎么样呢?shared_ptr我不必在实现文件中定义特殊的成员函数来避免未定义的行为?

Yak*_*ont 7

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

Widget::Widget(): pImpl(new Impl) {}
Run Code Online (Sandbox Code Playgroud)

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

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

所以你Impl完全定义的唯一函数是那些pImplT*某种类型创建的函数.