std :: shared_ptr在空指针上调用非默认删除器 [英] std::shared_ptr calls non-default deleter on null pointer

查看:101
本文介绍了std :: shared_ptr在空指针上调用非默认删除器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请参见以下示例:

  #include< iostream>#include< memory>Foo类{上市:Foo(){std :: cout<<"Foo()\ n";}〜Foo(){std :: cout<<〜Foo()\ n";}};int main(){自动删除器= [](Foo * p){if(!p){std :: cout<<在nullptr上调用deleter \ n";}删除p;};std :: shared_ptr< Foo>foo;std :: cout<<"\ n使用非空Foo:\ n";foo = std :: shared_ptr< Foo>(new Foo,deleter);std :: cout<<"foo是"<<(foo?"not":")<<"null \ n";std :: cout<<使用count ="<<foo.use_count()<<'\ n';foo.reset();std :: cout<<"\ n使用nullptr和deleter:\ n";foo = std :: shared_ptr< Foo>(nullptr,deleter);std :: cout<<"foo是"<<(foo?"not":")<<"null \ n";std :: cout<<使用count ="<<foo.use_count()<<'\ n';foo.reset();std :: cout<<"\ n使用nullptr,不使用删除器:\ n";foo = std :: shared_ptr< Foo>(nullptr);std :: cout<<"foo是"<<(foo?"not":")<<"null \ n";std :: cout<<使用count ="<<foo.use_count()<<'\ n';foo.reset();} 

输出为:

 对于非null Foo:Foo()foo不为null使用次数= 1〜Foo()使用nullptr和deleter:foo为空使用次数= 1在nullptr上调用Deleter使用nullptr,不使用删除器:foo为空使用计数= 0 

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

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

解决方案

tl; dr:是的,这是有目的的.


这很微妙.

shared_ptr可以处于两种状态:

使用空指针构造 shared_ptr 实际上会导致它不是空的 get()返回 p 的意思是 get()返回 nullptr ,但这并不能使其为空./p>

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

我确切不知道为什么为什么.一方面,我可以看到在nullptr情况下防止删除程序被调用的情况,因为通常认为 shared_ptr(nullptr)是空的"(即使从技术上讲不是这样).另一方面,我可以看到一个让删除器根据需要做出决定的情况(带有分支的开销).

您应该在此处包括对null的检查.


[util.smartptr.shared.const] 中的一些法文:

template< Y类,D类>shared_ptr(Y * p,D d);
template< Y类,D类,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必须满足Cpp17Allocator的要求(表34).

10)效果:构造一个 shared_ptr 对象,该对象拥有对象 p 和删除程序 d . T 不是数组类型,第一和第二个构造函数使用 p 启用 shared_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 ,即使托管指针为null,也会调用 std :: shared_ptr 的删除器.

(请使用文档!)

See this example :

#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();
}

The output is :

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

Here we see that shared_ptr calls the contained deleter when it is initialized with nullptr and a custom deleter. It seems that, when initialized with a custom deleter, shared_ptr considers it is "owning" nullptr and thus tries to delete it when it would delete any other owned pointer. Though it does not happen when no deleter is specified.

Is this intended behavior ? If so, what is the reason behind this behavior ?

解决方案

tl;dr: Yes, it's intended.


This is pretty subtle.

A shared_ptr can be in two states:

Constructing a shared_ptr with a null pointer actually leads to it being not-empty! get() returning p means get() returning nullptr, but that doesn't make it empty.

Since the default deleter just does delete p, and delete nullptr is a no-op, this doesn't usually matter. But, as you have seen, you can observe this difference if you provide your own deleter.

I don't know exactly why this is. On the one hand I can see a case for preventing a deleter from being invoked in the nullptr case because one generally considers a shared_ptr(nullptr) to be "empty" (even though it technically is not); on the other hand, I can see a case for letting the deleter make this decision (with the accompanying overhead of a branch) if it wants to.

You're right to include a check for null here.


Some legalese from [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.

(Notice that there is no exemption for the case that !p.)

And from [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.

Sidenote: I think the confusion between the phrases "owns an object" and "owns a pointer" in the above passages is an editorial problem.


We can also see this documented on 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.

(Please use documentation!)

这篇关于std :: shared_ptr在空指针上调用非默认删除器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆