看这个例子:
#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,因此在它会删除任何其他拥有的指针时尝试删除它。尽管没有指定删除器时不会发生这种情况。
这是有意为之的行为吗?如果是这样,这种行为背后的原因是什么?
最佳答案
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) 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
.
(注意 !p
的情况没有豁免。)
来自[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.
旁注:我认为上述段落中短语“拥有一个对象”和“拥有一个指针”之间的混淆是一个编辑问题。
我们还可以在 cppreference.com's ~shared_ptr
article 上看到这个记录:
Unlike
std::unique_ptr
, the deleter ofstd::shared_ptr
is invoked even if the managed pointer is null.
(请使用文档!)
关于c++ - std::shared_ptr 在空指针上调用非默认删除器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63580192/