shared_ptr删除器类 - 为什么要复制删除器?

shared_ptr with deleter class - why deleter is copied?

本文关键字:删除 复制 为什么 ptr shared      更新时间:2023-10-16

假设我创建了一个带有自定义删除器的共享指针。在下面的代码中,我想检查删除器对象本身会发生什么:

struct A {
A() { std::cout << "An"; }
~A() { std::cout << "~An"; }
};
struct D {
D() {
std::cout << "Dn";
}
~D() {
std::cout << "~Dn";
}
D(const D&) {
std::cout << "D(D&)n";
}
void operator()(A* p) const {
std::cout << "D(foo)n";
delete p;
}
};
int main()
{
std::shared_ptr<A> p(new A, D());
}

我看到"删除器"类的D(const D&)~D()又调用了六次:

D
A
D(D&)
D(D&)
D(D&)
D(D&)
D(D&)
D(D&)
~D
~D
~D
~D
~D
~D
D(foo)
~A
~D

会发生什么?为什么需要复制这么多次?

我用 gcc 7.4 检查了你的代码,我得到了相同数量的析构函数调用。您观察到的是删除器对象在std::move(deleter)中移动了六次。

由于已将析构函数添加到类中,因此将禁用默认移动语义的自动生成,并且需要显式定义它们:

D(D&&) = default;
D& operator=(D&&) = default;

但是,即使使用移动语义,析构函数仍可调用多达六次。

删除器通常是一个带有operator()的空类。在优化的代码和编写良好的shared_ptr实现中,空类的删除器不占用空间,因此复制开销为零。

实现通常知道在优化程序完成其工作后,围绕对象复制是否昂贵,以及是否必须采取预防措施。

在所介绍的案例中,我猜您正在观察一个未优化的构建。您会看到实现如何通过多层函数调用传递删除程序。在优化的版本中,实际上不会复制任何内容,因为删除器类D为空。

创建自定义删除器很好奇,但另一方面,它缺乏优化,因为定义明确的共享指针将运行以实现删除功能。(+新(

如果你看一下shared_ptr在<memory>的实现,你可以了解如何准备你的删除程序。

请注意,智能指针的实现因编译器和所使用的库而异。(例如,您的代码给了我 3 份副本(

仍在调查为什么会发生这种情况,但是是的,weak_ptr是更好的选择,因为它会破坏由shared_ptr管理的对象形成的参考循环。