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

时间:2019-03-26 16:11:39

标签: c++

请参见以下示例:

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

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

1 个答案:

答案 0 :(得分:4)

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)要求:d的构造和用D初始化的类型std::move(d)的删除器不得抛出异常。表达式d(p)必须具有明确定义的行为,并且不得抛出异常。 A必须满足Cpp17Allocator的要求(表34)。

     

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

     

11)确保: d(p)

(请注意,use_­count() == 1 && get() == p的情况下没有豁免。)

并且来自!p

  

[util.smartptr.shared.dest]

     

1)效果:

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

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


我们还可以在cppreference.com's ~shared_ptr article上看到此文档:

  

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

(请使用文档!)