【问题标题】:std::shared_ptr calls non-default deleter on null pointerstd::shared_ptr 在空指针上调用非默认删除器
【发布时间】:2019-03-26 16:11:38
【问题描述】:

看这个例子:

#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 nullptr\n"; }
        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 ":"") << "null\n";
    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 ":"") << "null\n";
    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 ":"") << "null\n";
    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,因此在删除任何其他拥有的指针时尝试删除它。虽然没有指定删除器时不会发生。

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

【问题讨论】:

  • delete(nullptr) 是,afaik,完全有效,所以对于给定 shared_ptr 的任何值,它都会调用它给定的删除器,而不会浪费周期检查它是否是 @ 987654332@
  • Unlike std::unique_ptr, the deleter of std::shared_ptr is invoked even if the managed pointer is null.Source
  • @PhilM 这实际上不是what the standard says,尽管根据 as-if 规则(当没有给出删除器时)它可能会像你描述的那样做
  • 嗯,才发现stackoverflow.com/a/11164463/560648可能是骗子

标签: c++ shared-ptr smart-pointers reference-counting


【解决方案1】:

tl;dr:是的,这是故意的。


这很微妙。

一个 shared_ptr 可以有两种状态:

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

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

我不太清楚为什么会这样。一方面,我可以看到在 nullptr 案例中阻止调用删除器的案例,因为人们通常认为 shared_ptr(nullptr) 是“空的”(即使它在技术上不是);另一方面,如果需要,我可以看到让删除者做出这个决定的案例(伴随着分支的开销)。

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


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

template&lt;class Y, class D&gt; shared_ptr(Y* p, D d);
template&lt;class Y, class D, class A&gt; shared_ptr(Y* p, D d, A a);
template&lt;class D&gt; shared_ptr(nullptr_t p, D d);
template&lt;class D, class A&gt; shared_ptr(nullptr_t p, D d, A a);强>

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

10) 效果:构造一个shared_­ptr 对象,该对象拥有对象p 和删除器dT 不是数组类型时,第一个和第二个构造函数启用shared_­from_­thisp。第二个和第四个构造函数应使用a 的副本来分配内存供内部使用。如果抛出异常,则调用d(p)

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

(注意!p的情况没有豁免。)

来自[util.smartptr.shared.dest]

~shared_ptr();

1) 效果:

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

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


我们也可以在cppreference.com's ~shared_ptr article 上看到这一点:

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

(请使用文档!)

【讨论】:

  • 你是例外。
  • template&lt;class Y, class D&gt; shared_ptr(Y* p, D d); 要求 delete p 是一个有效的操作并且默认 delete nullptr 是。如果自定义删除器无法处理,那么将nullptr 传递给sharedptr 是没有意义的
  • 如果使用别名构造函数创建空的shared_ptr,则它可能有一个非空的存储指针 - 请参阅eel.is/c++draft/util.smartptr.shared#const-17
  • @SamerTufail 正确。
  • @yachoor 谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-03
  • 1970-01-01
  • 2012-08-17
  • 2016-11-20
  • 1970-01-01
相关资源
最近更新 更多