请参见以下示例:
#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,因此在删除任何其他拥有的指针时会尝试将其删除。尽管未指定删除器时不会发生。
这是预期的行为吗?如果是这样,此行为背后的原因是什么?
答案 0 :(得分:4)
tl; dr:是的。
这很微妙。
shared_ptr可以处于两种状态:
get()
可能会返回nullptr
(尽管some ctors exist which change this postcondition)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
的构造和用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
的删除器。
(请使用文档!)