在shared_ptr的自定义删除器中检查nullptr是否有意义?

时间:2017-03-22 20:56:51

标签: c++ c++11 shared-ptr

我见过一些代码使用std::shared_ptr和自定义删除器来测试nullptr的参数,例如MyClass,它有一个close()方法并且用一些{构造{1}}:

CreateMyClass

在删除器中测试auto pMyClass = std::shared_ptr<MyClass>(CreateMyClass(), [](MyClass* ptr) { if(ptr) ptr->close(); }); 的null-ness是否有意义? 这会发生吗?如何?

3 个答案:

答案 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返回nullptrpMyClassuse_count)的引用计数变为1。当pMyClass被销毁时,following will happens

  

如果*this拥有一个对象并且它是拥有它的最后一个shared_ptr,那么   对象通过拥有的删除器销毁。

因此,如果自定义删除器取消引用由代码中的shared_ptr(ptr->close())保存的指针,那么它应该处理nullptr检查。

请注意empty shared_ptr is not the same as null shared_ptr

答案 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{});
}

Live example

这会打印"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_ptrnullptr的驱逐舰被转移到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更容易,但我不确定我是否做得很好。

Live example

C ++ 11版本需要手动编写optional等。