使用shared_ptr处理不完整类型的Pimpl习惯用法 [英] Pimpl idiom using shared_ptr working with incomplete types

查看:168
本文介绍了使用shared_ptr处理不完整类型的Pimpl习惯用法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm reading Effective Modern C++ by Scott Meyers and he's discussing the use of the pimpl idiom and pointing to the implementation class with unique_ptr, but there is an issue of special member functions (such as destructors) requiring the type to be complete. This is because unique_ptr's default deleter statically asserts whether the type to be deleted is complete, before delete p is used. So any special member functions of the class must be defined in the implementation file (rather than being compiler-generated), after the implementation class has been defined.

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

At the end of the chapter, he mentions there is no need to define special member functions in the implementation file if the smart pointer used is shared_ptr, and this stems from the way it supports a custom deleter. To quote:

std :: unique_ptr和std :: shared_ptr之间的行为差​​异 pImpl指针源自这些智能指针支持自定义的不同方式 删除器.对于std :: unique_ptr,删除器的类型是智能类型的一部分 指针,这使编译器可以生成较小的运行时数据 结构和更快的运行时代码.这种更高效率的结果是 编译器生成的特殊功能(例如, 使用析构函数或移动操作).对于std :: 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在类未完成的情况下仍然可以工作.似乎使用shared_ptr时没有编译器错误的唯一原因是因为没有像unique_ptr那样的静态断言,并且由于缺少断言而可能发生未定义的运行时行为.

Despite this, I still can't see why shared_ptr could still work without the class being complete. It seems like the only reason there is no compiler error when using shared_ptr is because there is no static assertion like unique_ptr had, and that undefined runtime behaviour could instead occur because of this lack of assertion.

我不知道shared_ptr的析构函数的实现,但是(通过阅读C ++ Primer)我得到的印象是它的工作原理类似于:

I don't know the implementation of the shared_ptr's destructor, but (from reading C++ Primer) I gathered the impression it works something like:

del ? del(p) : delete p;

del是自定义删除器的指针或函数对象. Cppreference 还可以清除shared_ptr析构函数,而无需自定义删除器使用delete p

Where del is a pointer or function object to the custom deleter. Cppreference also makes it clear the shared_ptr destructor with no custom deleter uses delete p

3)如果T不是数组类型,则使用delete-expression delete ptr; .... Y必须为完整类型. delete表达式必须格式正确,行为明确,并且不会引发任何异常.

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惯用语的一个最小示例:

Emphasis on the fact that the deleted type must be complete. A minimal example of the pimpl idiom:

//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时,将为Widget类型(在main.cpp之内)实例化shared_ptr的模板,并且可能为shared_ptr生成的已编译析构函数包含行,因为我没有提供自定义删除器.但是,此时仍未定义Impl,但仍执行了行delete pImpl.当然,这是不确定的行为吗?

When Widget a in main.cpp is compiled, the template of shared_ptr is instantited for type Widget (within main.cpp) and presumably the resulting compiled destructor for shared_ptr contains execution of the line delete pImpl, because I have not supplied a custom deletor. However at that point, Impl still has not been defined, yet the line delete pImpl is executed. This, surely, is undefined behaviour?

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

So how is it that when using the pimpl idiom with shared_ptr, I don't have to define the special member functions in the implementation file to avoid undefined behaviour?

推荐答案

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

The deleter for a shared pointer is created here:

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

直到这一点,所有共享指针都具有与std::funciton<void(Impl*)>相同的作用.

until that point, all the shared pointer has is the equivalent of a std::funciton<void(Impl*)>.

当使用T*构造shared_ptr时,它将编写一个删除程序并将其存储在等效的std::function中.那时类型必须是完整的.

When you construct a shared_ptr with a T*, it writes a deleter and stores it in the std::function equivalent. At that point the type must be complete.

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

So the only functions you have to define after Impl is fully defined are those that create a pImpl from a T* of some kind.

这篇关于使用shared_ptr处理不完整类型的Pimpl习惯用法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆