我见过一些代码使用std::shared_ptr
和自定义删除器来测试nullptr的参数,例如MyClass
,它有一个close()
方法并且用一些{构造{1}}:
CreateMyClass
在删除器中测试auto pMyClass = std::shared_ptr<MyClass>(CreateMyClass(),
[](MyClass* ptr)
{
if(ptr)
ptr->close();
});
的null-ness是否有意义?
这会发生吗?如何?
答案 0 :(得分:8)
构造函数 packet.sniffed_on
要求std::shared_ptr<T>::shared_ptr(Y*p)
是有效的操作。当delete p
等于p
时,这是有效的操作。
构造函数nullptr
要求std::shared_ptr<T>::shared_ptr(Y*p, Del del)
是有效的操作。
如果您的自定义删除工具无法处理del(p)
等于p
,那么在nullptr
的构造函数中传递空p
无效。
您提供的构造函数可以更好地呈现,因此:
shared_ptr
请注意,现在shared_ptr不可能拥有一个空对象。
答案 1 :(得分:7)
是的,实际上是有道理的。假设CreateMyClass
返回nullptr
。 pMyClass
(use_count
)的引用计数变为1
。当pMyClass
被销毁时,following will happens:
如果
*this
拥有一个对象并且它是拥有它的最后一个shared_ptr
,那么 对象通过拥有的删除器销毁。
因此,如果自定义删除器取消引用由代码中的shared_ptr(ptr->close()
)保存的指针,那么它应该处理nullptr检查。
答案 2 :(得分:2)
struct deleter {
template<class T>
void operator()(T*) const {
std::cout << "deleter run\n";
}
};
int main() {
std::shared_ptr<int> bob((int*)0, deleter{});
}
这会打印"deleter run\n"
。删除器确实在运行。
empty 的概念和拥有nullptr 的概念是shared_ptr
的不同概念。
bob
非为空,但bob.get()==nullptr
。当非为空时,将调用析构函数。
int main() {
int x;
std::shared_ptr<int> alice( std::shared_ptr<int>{}, &x );
}
alice
为空,但alice.get() != nullptr
。当alice
超出范围时,delete &x
不运行(实际上没有运行析构函数)。
只有从未使用空指针和删除器构造共享指针时,才能避免这种情况。
解决此问题的一种方法是首先使用自定义删除器创建唯一指针。
template<class Deleter, class T>
std::unique_ptr<T, Deleter> change_deleter( std::unique_ptr<T> up, Deleter&& deleter={} ) {
return {up.release(), std::forward<Deleter>(deleter)};
}
struct close_and_delete_foo; // closes and deletes a foo
std::unique_ptr<foo, close_and_delete_foo> make_foo() {
auto foo = std::make_unique<foo>();
if (!foo->open()) return {};
return change_deleter<close_and_delete_foo>(std::move(foo));
}
与shared_ptr
不同,unique_ptr
无法保持nullptr
“非空”(标准不会将{em>空用于{{1}而是谈论unique_ptr
)。
.get()==nullptr
可以隐式转换为unique_ptr
。如果它有shared_ptr
,则生成的nullptr
为空,而不仅仅是shared_ptr
。 nullptr
的驱逐舰被转移到unique_ptr
。
所有这些技术的缺点是shared_ptr
引用计数内存块是对象内存块的单独分配。两次分配比一次更差。
但是shared_ptr
构造函数不允许您传入自定义删除器。
如果破坏你的对象不能抛出,你可以非常小心地使用别名构造函数:
make_shared
这里我们设法使用一个块进行驱逐舰和引用计数,同时允许可能失败的// empty base optimization enabled:
template<class T, class D>
struct special_destroyed:D {
std::optional<T> t;
template<class...Ds>
special_destroyed(
Ds&&...ds
):
D(std::forward<Ds>(ds)...)
{}
~special_destroyed() {
if (t)
(*this)(std::addressof(*t));
}
};
std::shared_ptr<MyClass> make_myclass() {
auto r = std::make_shared< special_destroyed<MyClass, CloseMyClass> >();
r->t.emplace();
try {
if (!r->t->open())
return {};
} catch(...) {
r->t = std::nullopt;
throw;
}
return {r, std::addressof(*r.t)};
}
操作,并且仅在数据实际存在时自动open
。
请注意,驱逐舰只应 关闭closing
,而不是删除它;删除由MyClass
包裹make_shared
的外部驱逐舰处理。
这对special_destroyed
使用C ++ 17,但std::optional
和其他地方可以使用其他optional
。
原始C ++ 14解决方案。我们创建了一个粗略的boost
:
optional
这可能太过分了。幸运的是,我们极其有限的template<class T, class D>
struct special_delete:D {
using storage = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
storage data;
bool b_created = false;
template<class...Ts>
void emplace(Ts&&...ts) {
::new( (void*)&data ) T(std::forward<Ts>(ts)...);
b_created=true;
}
template<std::size_t...Is, class Tuple>
void emplace_from_tuple( std::index_sequence<Is...>, Tuple&&tup ) {
return emplace( std::get<Is>(std::forward<Tuple>(tup))... );
}
T* get() {
if (b_created)
return reinterpret_cast<T*>(&data);
else
return nullptr;
}
template<class...Ds>
special_delete(Ds&&...ds):D(std::forward<Ds>(ds)...){}
~special_delete() {
if (b_created)
{
(*this)( get() );
get()->~T();
}
}
};
struct do_nothing {
template<class...Ts>
void operator()(Ts&&...)const{}
};
template<class T, class D, class F=do_nothing, class Tuple=std::tuple<>, class...Ds>
std::shared_ptr<T> make_special_delete(
F&& f={},
Tuple&& args=std::tuple<>(),
Ds&&...ds
) {
auto r = std::make_shared<special_delete<T,D>>(std::forward<Ds>(ds)...);
r->emplace_from_tuple(
std::make_index_sequence<
std::tuple_size<std::remove_reference_t<Tuple>>::value
>{},
std::move(args)
);
try {
f(*r->get());
} catch(...) {
r->b_created = false;
r->get()->~T();
throw;
}
return {r, r->get()};
}
可以写得比真正的optional
更容易,但我不确定我是否做得很好。
C ++ 11版本需要手动编写optional
等。