参见这个例子:
#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 nullptrn"; }
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 ":"") << "nulln";
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 ":"") << "nulln";
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 ":"") << "nulln";
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;大卫:是的,这是有意的。
这是非常微妙的。
shared_ptr可以处于两种状态:
- "空":默认构造或重置; 没有所有权;
get()
可能会返回nullptr
(尽管存在一些更改此后置条件的 CTOR) - 不为空:持有指针
p
的所有权;get()
返回p
.
使用空指针构造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
和用std::move(d)
初始化的D
类型的删除器不得引发异常。表达式d(p)
应具有明确定义的行为,并且不应引发异常。A 应满足 CPP17 分配器要求(表 34)。10) 效果:构造一个拥有对象
p
和删除器d
的shared_ptr
对象。当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
不同,即使托管指针为空,也会调用std::shared_ptr
的删除器。
(请使用文档!