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

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

看这个例子:

#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,因此在它会删除任何其他拥有的指针时尝试删除它。尽管没有指定删除器时不会发生这种情况。

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

最佳答案

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


这很微妙。

shared_ptr 可以处于两种状态:

构建 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) Requires: Construction of d and a deleter of type D initialized with std::move(d) shall not throw exceptions. The expression d(p) shall have well-defined behavior and shall not throw exceptions. A shall satisfy the Cpp17Allocator requirements (Table 34).

10) Effects: Constructs a shared_­ptr object that owns the object p and the deleter d. When T is not an array type, the first and second constructors enable shared_­from_­this with p. The second and fourth constructors shall use a copy of a to allocate memory for internal use. If an exception is thrown, d(p) is called.

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

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

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

~shared_ptr();

1) Effects:

  • If *this is empty or shares ownership with another shared_­ptr instance (use_­count() > 1), there are no side effects.
  • Otherwise, if *this owns an object p and a deleter d, d(p) is called.
  • Otherwise, *this owns a pointer p, and delete p is called.

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


我们还可以在 cppreference.com's ~shared_ptr article 上看到这个记录:

Unlike std::unique_ptr, the deleter of std::shared_ptr is invoked even if the managed pointer is null.

(请使用文档!)

关于c++ - std::shared_ptr 在空指针上调用非默认删除器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63580192/

相关文章:

C++ shared_ptr 和内置指针

c++ - 如何创建锁定和解锁互斥锁的智能指针?

c++ - QSerialPort 5.2 中的 SerialPortError?

c++ - 如何将 boost::atomic_store 与 shared_ptr<T> 和 shared_ptr<const T> 一起使用?

c++ - 锁定 shared_ptr

c++ - 一旦你采用了 boost 的智能指针,有没有使用原始指针的情况?

c++ - std::auto_ptr 的最佳实践

c++ - 在 C++ 中标记类/方法已过时或弃用

c++节点列表-NULL测试不起作用

c++ - 如何获取应用于本地工作站的组策略对象列表