【问题标题】:Pimpl idiom using shared_ptr working with incomplete types使用 shared_ptr 处理不完整类型的 Pimpl 习惯用法
【发布时间】:2017-03-29 22:25:26
【问题描述】:

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

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

std::unique_ptr 和 std::shared_ptr 的行为差异 pImpl 指针源于这些智能指针支持自定义的不同方式 删除者。对于 std::unique_ptr,删除器的类型是 smart 类型的一部分 指针,这使得编译器可以生成更小的运行时数据 结构和更快的运行时代码。这种更高效率的结果是 当编译器生成的特殊函数(例如, 析构函数或移动操作)被使用。对于 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) 如果T 不是数组类型,则使用删除表达式delete ptr; .... 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;
}

main.cpp 中的Widget a 被编译时,shared_ptr 的模板被实例化为类型Widget(在main.cpp 内)并且可能为shared_ptr 生成的编译析构函数包含@987654343 行的执行@,因为我没有提供自定义删除器。然而此时,Impl 仍未定义,但行 delete pImpl 已被执行。这肯定是未定义的行为吗?

那么,当使用带有shared_ptr 的pimpl 习惯用法时,我如何不必在实现文件中定义特殊的成员函数来避免未定义的行为?

【问题讨论】:

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


    【解决方案1】:

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

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

    在那之前,所有共享指针都相当于std::funciton&lt;void(Impl*)&gt;

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

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

    【讨论】:

    • 我明白了,所以我假设shared_ptr 析构函数直接调用delete(如果没有提供自定义)是错误的,它仍然通过函数对象调用它。谢谢。
    • 虽然我必须要求在我的脑海中清除这一点,但当您说删除器是在“此处”创建时,我认为这是通过闭包完成的?我找不到shared_ptr 的确切默认删除器的详细信息(尽管我看到unique_ptr 使用std::default_delete)。
    • @searg 效果是指定的,而不是实现。
    • 通过引入额外的删除函数对象包装器来解耦函数的定义(在 share_ptr 的 ctor 中)和用法(在 shared_ptr 的 dctor 中),此设计选择必须仅适用于 Pimp。很好的解释!我希望迈耶斯能像你一样把故事讲好。
    猜你喜欢
    • 2011-06-22
    • 2015-05-01
    • 2016-05-05
    • 1970-01-01
    • 1970-01-01
    • 2019-01-19
    • 1970-01-01
    • 2013-07-13
    • 1970-01-01
    相关资源
    最近更新 更多