【问题标题】:shared_ptr magic :)shared_ptr 魔法 :)
【发布时间】:2011-04-23 10:08:57
【问题描述】:

Mr. Lidström and I had an argument :)

先生。 Lidström 声称构造 shared_ptr<Base> p(new Derived); 不需要 Base 具有虚拟析构函数:

Armen Tsirunyan:“真的吗?shared_ptr 会正确清理吗?您能否在这种情况下演示如何实现该效果?”

Daniel Lidström:“shared_ptr 使用自己的析构函数来删除 Concrete 实例。这在 C++ 社区中被称为 RAII。我的建议是你学习所有您可以了解 RAII。当您在所有情况下使用 RAII 时,它将使您的 C++ 编码变得更加容易。"

Armen Tsirunyan:“我知道 RAII,我也知道最终 shared_ptr 析构函数可能会在 pn 达到 0 时删除存储的 px。但是如果 px 有静态指向Base 的类型指针和指向Derived 的动态类型指针,那么除非Base 具有虚拟析构函数,否则这将导致未定义的行为。如果我错了,请纠正我。”

Daniel Lidström:“shared_ptr 知道静态类型是具体的。它知道这一点,因为我在它的构造函数中传递了它!看起来有点像魔术,但我可以向您保证,这是设计使然,非常好。”

所以,判断我们。如何在不要求多态类具有虚拟析构函数的情况下实现 shared_ptr (如果是的话)? 提前致谢

【问题讨论】:

  • 你可以链接到original thread
  • 另一个有趣的事情是shared_ptr<void> p(new Derived) 也会通过它的析构函数来销毁Derived 对象,不管它是否是virtual
  • 提问的好方法:)
  • 尽管 shared_ptr 允许这样做,但是将一个类设计为没有虚拟 dtor 的基础是一个非常糟糕的主意。 Daniel 关于 RAII 的 cmets 具有误导性——它与此无关——但引用的对话听起来像是一个简单的误解(以及对 shared_ptr 如何工作的错误假设)。
  • 不是 RAII,而是类型擦除析构函数。您必须小心,因为 shared_ptr<T>( (T*)new U() ) 其中 struct U:T 不会做正确的事情(这可以很容易地间接完成,例如采用 T* 并传递 U* 的函数)跨度>

标签: c++ destructor smart-pointers


【解决方案1】:

是的,可以这样实现 shared_ptr 。 Boost 确实如此,C++11 标准也需要这种行为。作为一个额外的灵活性,shared_ptr 管理的不仅仅是一个引用计数器。所谓的删除器通常被放入同样包含引用计数器的内存块中。但有趣的是这个删除器的类型不是 shared_ptr 类型的一部分。这称为“类型擦除”,基本上与用于实现“多态函数”boost::functionstd::function 以隐藏实际函子类型的技术相同。为了使您的示例正常工作,我们需要一个模板化构造函数:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

所以,如果你在你的课程BaseDerived 中使用它...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... 带有Y=Derived 的模板化构造函数用于构造shared_ptr 对象。因此,构造函数有机会创建适当的删除器对象和引用计数器,并将指向该控制块的指针存储为数据成员。如果引用计数器达到零,将使用先前创建的 Derived-aware 删除器来处理该对象。

C++11 标准对这个构造函数 (20.7.2.2.1) 有以下说法:

要求: p 必须可转换为 T*Y 应该是一个完整的类型。 表达式delete p 应格式正确、行为明确且不抛出异常。

效果:构造一个shared_ptr对象拥有指针p

对于析构函数(20.7.2.2.2):

效果:如果*this或与另一个shared_ptr 实例(use_count() &gt; 1) 共享所有权,则没有副作用。 否则,如果*this 拥有一个对象p 和一个删除器d,则调用d(p)否则,如果*this拥有指针p,则调用delete p

(我用粗体强调)。

【讨论】:

  • the upcoming standard also requires this behaviour:(a)哪个标准和(b)你能提供一个参考(标准)吗?
  • 我只想在@sellibitze 的答案中添加评论,因为我没有足够的点数给add a comment。 IMO,它比the Standard requires 更多Boost does this。根据我的理解,我认为标准并不要求这样做。谈到@sellibitze 的示例shared_ptr&lt;Base&gt; sp (new Derived);constructor需要 只要求delete Derived 定义良好且格式良好。对于destructor的规范,还有一个p,但我认为不是指constructor规范中的p
【解决方案2】:

创建 shared_ptr 时,它会在其内部存储一个 deleter 对象。当 shared_ptr 即将释放指向的资源时调用此对象。由于您知道如何在构建时销毁资源,因此您可以将 shared_ptr 与不完整的类型一起使用。创建 shared_ptr 的人在那里存储了一个正确的删除器。

例如,您可以创建自定义删除器:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

p 将调用 DeleteDerived 来销毁指向的对象。实现会自动执行此操作。

【讨论】:

  • +1 表示关于不完整类型的注释,在使用 shared_ptr 作为属性时非常方便。
【解决方案3】:

简单地说,

shared_ptr 使用由构造函数创建的特殊删除函数,该函数始终使用 给定对象的析构函数而不是 Base 的析构函数,这在模板元编程中有点工作,但它可以工作。

类似的东西

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}

【讨论】:

  • 嗯...有趣,我开始相信这一点 :)
  • @Armen Tsirunyan 在开始讨论之前,您应该已经查看了 shared_ptr 的设计描述。这种“删除器的捕获”是 shared_ptr 的基本特征之一......
  • @paul_71:我同意你的看法。另一方面,我相信这个讨论不仅对我有用,而且对其他不知道 shared_ptr 事实的人也有用。所以我想无论如何开始这个线程并不是什么大罪:)
  • @Armen 当然不是。相反,您很好地指出了 shared_ptr 的这个非常非常重要的特性,即使是有经验的 c++ 开发人员也经常监督它。
猜你喜欢
  • 1970-01-01
  • 2017-04-30
  • 2014-05-09
  • 2014-12-14
  • 1970-01-01
相关资源
最近更新 更多