使用shared_ptr处理不完整类型的Pimpl习惯用法 [英] Pimpl idiom using shared_ptr working with incomplete types
问题描述
我正在阅读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-expressiondelete ptr
; .... Y必须为完整类型. delete表达式必须格式正确,行为明确,并且不会引发任何异常.
3) Uses the delete-expression
delete ptr
ifT
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屋!