std :: shared_ptr在空指针上调用非默认删除器 [英] std::shared_ptr calls non-default deleter on null pointer
问题描述
请参见以下示例:
#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可以处于两种状态:
- 空":默认构造或重置;不拥有所有权;
get()
可能会返回nullptr
(尽管存在一些可更改此后置条件的ctor ) - 不为空:持有指针
p
的所有权;get()
返回p
.
使用空指针构造 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:
- "empty": default-constructed or reset; holds no ownership;
get()
may returnnullptr
(although some ctors exist which change this postcondition) - not empty: holds ownership of a pointer
p
;get()
returnsp
.
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 typeD
initialized withstd::move(d)
shall not throw exceptions. The expressiond(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 objectp
and the deleterd
. WhenT
is not an array type, the first and second constructors enableshared_from_this
withp
. The second and fourth constructors shall use a copy ofa
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 anothershared_ptr
instance (use_count() > 1
), there are no side effects.- Otherwise, if
*this
owns an objectp
and a deleterd
,d(p)
is called.- Otherwise,
*this
owns a pointerp
, anddelete 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 ofstd::shared_ptr
is invoked even if the managed pointer is null.
(Please use documentation!)
这篇关于std :: shared_ptr在空指针上调用非默认删除器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!