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

Ann*_*nyo 6 c++ smart-pointers reference-counting shared-ptr

看这个例子:

#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();
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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
Run Code Online (Sandbox Code Playgroud)

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

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

Lig*_*ica 6

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

\n\n
\n\n

这非常微妙。

\n\n

Shared_ptr 可以处于两种状态:

\n\n\n\n

shared_ptr使用空指针构造 a实际上会导致它不为空get()回归的p意思是get()归来nullptr,但这并不会使它为空。

\n\n

由于默认删除器只是执行delete p, 和delete nullptr是无操作,因此这通常并不重要。但是,正如您所看到的,如果您提供自己的删除程序,您可以观察到这种差异。

\n\n

我不知道这是为什么。一方面,我可以看到一种防止在 nullptr 情况下调用删除器的情况,因为人们通常认为 ashared_ptr(nullptr)是“空”(尽管技术上它不是);另一方面,如果愿意的话,我可以看到让删除者做出这个决定的情况(伴随着分支的开销)。

\n\n

您在这里包含对 null 的检查是正确的。

\n\n
\n\n

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

\n\n
\n

template<class Y, class D> shared_ptr(Y* p, D d);
\n \n \n 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);

\n\n

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

\n\n

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

\n\n

11) 确保:use_\xc2\xadcount() == 1 && get() == p.

\n
\n\n

(请注意,以下情况没有豁免!p。)

\n\n

并来自[util.smartptr.shared.dest]

\n\n
\n

~shared_ptr();

\n\n

1)效果:

\n\n
    \n
  • 如果*this为空或与另一个shared_\xc2\xadptr实例共享所有权 ( use_\xc2\xadcount() > 1),则没有副作用。
  • \n
  • 否则,如果*this拥有一个对象p和一个删除器dd(p)则被调用。
  • \n
  • 否则,*this拥有一个指针p,并被delete p调用。
  • \n
\n
\n\n

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

\n\n
\n\n

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

\n\n
\n

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

\n
\n\n

(请使用文档!)

\n