shared_ptr魔术:)


89

利德斯特罗姆先生和我有一个论点:)

Lidström先生的主张是,构造shared_ptr<Base> p(new Derived);不需要Base具有虚拟析构函数:

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

DanielLidström:“ shared_ptr使用其自己的析构函数来删除Concrete实例。在C ++社区中被称为RAII。我的建议是,您将学习到有关RAII的全部知识。它将使您使用C ++的代码如此容易在任何情况下都可以使用RAII。”

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

DanielLidström:“ shared_ptr知道静态类型是Concrete。自从我在其构造函数中传递它以来,它就知道这一点!看起来有点像魔术,但是我可以向您保证,它是设计使然,非常好。”

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


3
您可以链接到原始线程
Darin Dimitrov

8
另一个有趣的事情是,无论对象是否析构,它shared_ptr<void> p(new Derived)也会Derived通过其析构函数销毁该对象virtual
dalle 2010年

7
提出问题的好方法:)
rubenvb 2010年

5
即使shared_ptr允许这样做,将类设计为没有虚拟dtor的基础也是一个非常糟糕的主意。Daniel关于RAII的评论具有误导性-与之无关-但是引用的对话听起来像是一种简单的沟通不畅(以及对shared_ptr工作原理的错误假设)。

6
不是RAII,而是类型擦除器。您必须小心,因为shared_ptr<T>( (T*)new U() )在哪里struct U:T做不正确的事情(并且可以轻松地间接完成,例如接受aT*并传递a的函数U*
Yakk-Adam Nevraumont 2014年

Answers:


74

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

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

因此,如果您将其与Base和Derived类一起使用,则...

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

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

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

关于此构造函数(20.7.2.2.1),C ++ 11标准具有以下说法:

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

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

对于析构函数(20.7.2.2.2):

效果:如果*this或与另一个shared_ptr实例(use_count() > 1)共享所有权,则没有副作用。否则,如果*this拥有一个对象p和一个deleteer dd(p)则被调用。 否则,如果*this拥有一个指针pdelete p则被调用。

(强调使用粗体是我的)。


the upcoming standard also requires this behaviour:(a)哪个标准和(b)您可以提供(该标准的)参考?
kevinarpe

我只想在@sellibitze的答案中添加评论,因为我没有足够的要点add a comment。海事组织,Boost does this不仅仅是the Standard requires。我认为标准不是我所理解的要求。谈论@sellibitze的例子shared_ptr<Base> sp (new Derived);需要constructor只是要求delete Derived是良好限定和良好地形成。对于的规范destructor,也有一个p,但我认为它不是指p的规范中的constructor
翁鲁俊

28

创建shared_ptr时,它将在内部存储一个删除器对象。当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销毁指向的对象。该实现会自动执行此操作。


4
+1表示不完整类型,使用ashared_ptr作为属性时非常方便。
Matthieu M.

16

只是,

shared_ptr 使用由构造函数创建的特殊删除函数,该函数始终使用给定对象的析构函数而不是Base的析构函数,这与模板元编程有关,但确实可行。

像这样

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

1
嗯...有趣,我开始相信这一点:)
Armen Tsirunyan

1
@Armen Tsirunyan在开始讨论之前,您应该已经对shared_ptr的设计描述有所了解。这种“删除者的捕获”是shared_ptr的基本功能之一
Paul Michalik 2010年

6
@ paul_71:我同意你的看法。另一方面,我认为这种讨论不仅对我有用,而且对不知道shared_ptr这个事实的其他人也很有用。所以我想无论如何启动这个线程不是一个大罪过:)
Armen Tsirunyan 2010年

3
@Armen当然不是。相反,您在指出shared_ptr <T>的这一非常非常重要的功能方面做得很好,即使有经验的c ++开发人员也经常监督该功能。
Paul Michalik 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.