std::shared_ptr 在空指针上调用非默认删除程序

std::shared_ptr calls non-default deleter on null pointer

本文关键字:调用 删除程序 默认 shared ptr std 空指针      更新时间:2023-10-16

参见这个例子:

#include <iostream>
#include <memory>
class Foo {
public:
Foo()  { std::cout << "Foo()n";  }
~Foo() { std::cout << "~Foo()n"; }
};
int main(){
auto deleter = [](Foo* p) {
if(!p) { std::cout << "Calling deleter on nullptrn"; }
delete p;
};
std::shared_ptr<Foo> foo;
std::cout << "nWith non-null Foo:n";
foo = std::shared_ptr<Foo>(new Foo, deleter);
std::cout << "foo is " << (foo ? "not ":"") << "nulln";
std::cout << "use count=" << foo.use_count() << 'n';
foo.reset();
std::cout << "nWith nullptr and deleter:n";
foo = std::shared_ptr<Foo>(nullptr, deleter);
std::cout << "foo is " << (foo ? "not ":"") << "nulln";
std::cout << "use count=" << foo.use_count() << 'n';
foo.reset();
std::cout << "nWith nullptr, without deleter:n";
foo = std::shared_ptr<Foo>(nullptr);
std::cout << "foo is " << (foo ? "not ":"") << "nulln";
std::cout << "use count=" << foo.use_count() << 'n';
foo.reset();
}

输出为:

With non-null Foo:
Foo()
foo is not null
use count=1
~Foo()
With nullptr and deleter:
foo is null
use count=1
Calling deleter on nullptr
With nullptr, without deleter:
foo is null
use count=0

在这里,我们看到shared_ptr在使用nullptr和自定义删除器初始化时调用包含的删除器。 似乎,当使用自定义删除器初始化时,shared_ptr认为它是"拥有"nullptr,因此在删除任何其他拥有的指针时尝试删除它。尽管未指定删除程序时不会发生这种情况。

这是预期的行为吗?如果是这样,这种行为背后的原因是什么?

tl;大卫:是的,这是有意的。


这是非常微妙的。

shared_ptr可以处于两种状态:

  • "空":默认构造或重置; 没有所有权;get()可能会返回nullptr(尽管存在一些更改此后置条件的 CTOR)
  • 不为空:持有指针p的所有权;get()返回p.

使用空指针构造shared_ptr实际上会导致它不为空get()返回p意味着get()返回nullptr,但这并不表示它为空。

由于默认删除器只是delete p,并且delete nullptr是无操作,因此这通常无关紧要。但是,如您所见,如果您提供自己的删除器,则可以观察到这种差异。

我不知道这是为什么。一方面,我可以看到在 nullptr 情况下阻止调用删除器的情况,因为人们通常认为shared_ptr(nullptr)是"空的"(即使它在技术上不是);另一方面,如果删除者愿意,我可以看到让删除者做出此决定(附带分支开销)的情况。

您在此处包含对 null 的检查是正确的。


一些来自[util.smartptr.shared.const]的法律术语:


template<class Y, class D> shared_ptr(Y* p, D d);template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D> shared_ptr(nullptr_t p, D d);template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

9) 要求:构造d和用std::move(d)初始化的D类型的删除器不得引发异常。表达式d(p)应具有明确定义的行为,并且不应引发异常。A 应满足 CPP17 分配器要求(表 34)。

10) 效果:构造一个拥有对象p和删除器dshared_­ptr对象。T不是数组类型时,第一个和第二个构造函数会启用带有pshared_­from_­this。第二个和第四个构造函数应使用a的副本来分配内存以供内部使用。如果引发异常,则调用d(p)

11) 确保:use_­count() == 1 && get() == p.

(请注意,对于!p的情况没有豁免。

[util.smartptr.shared.dest]

~shared_ptr();

1)效果:

  • 如果*this为空或与另一个shared_­ptr实例(use_­count() > 1)共享所有权,则没有副作用。
  • 否则,如果*this拥有对象p和删除程序d,则调用d(p)
  • 否则,*this拥有一个指针p,并调用delete p

旁注:我认为上述段落中"拥有对象"和"拥有指针"这两个短语之间的混淆是一个编辑问题。


我们还可以在cppreference.com的~shared_ptr文章中看到这一点:

std::unique_ptr不同,即使托管指针为空,也会调用std::shared_ptr的删除器。

(请使用文档!