unique_ptr和shared_ptr中的Deleter类型区别

28

当我发现标准在指针所拥有的Deleter方面对std::unique_ptrstd::shared_ptr定义了两种完全不同的方式时,我感到非常好奇。下面是来自cppreference::unique_ptrcppreference::shared_ptr的声明:

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

template< class T > class shared_ptr;

正如您所看到的,unique_ptr将Deleter对象的类型作为模板参数“保存”。 这也可以在稍后从指针中检索Deleter的方式中看出:

// unique_ptr has a member function to retrieve the Deleter
template<
    class T,
    class Deleter = std::default_delete<T>
>
Deleter& unique_ptr<T, Deleter>::get_deleter();

// For shared_ptr this is not a member function
template<class Deleter, class T>
Deleter* get_deleter(const std::shared_ptr<T>& p);

能有人解释一下这种差异背后的理由吗?我显然倾向于使用unique_ptr,为什么不把这个概念也应用到shared_ptr中呢?此外,为什么在后一种情况下get_deleter会成为一个非成员函数?


2
有人需要挖出原始提案,但我的猜测是:不将删除器作为模板参数使shared_ptr更易于使用,但您需要支付类型抹除成本。将get_deleter设置为成员将使编写接受shared_ptr<T>的通用代码更加繁琐-您需要编写sp.template get_deleter<Deleter>()而不是get_deleter<Deleter>(sp)。这就是为什么std::get是非成员的原因。 - T.C.
3
稍微扩展一下 @T.C. 的说法,unique_ptr 的设计目标之一是几乎没有开销。擦除删除器的类型很方便,但会引入运行时开销,因此对于 unique_ptr 而言不如对于 shared_ptr 合适。 - wakjah
你还应该注意到,由于这种差异,即使 Base 没有虚析构函数,shared_ptr<Base> p = make_shared<Derived>() 也会做正确的事情。证明 - Benoît
1个回答

27

在这里您可以找到智能指针的原始提案:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1450.html

它相当明确地回答了您的问题:

由于删除器不是类型的一部分,因此更改分配策略不会破坏源或二进制兼容性,并且不需要客户端重新编译。

这也很有用,因为可以为std::shared_ptr的客户端提供更多灵活性,例如可以在同一容器中存储具有不同删除器的shared_ptr实例。

此外,因为shared_ptr实现需要一个共享内存块(用于存储引用计数),并且因为与原始指针相比必须存在一些开销,所以添加类型擦除的删除器在这里并不是什么大问题。

另一方面,unique_ptr旨在完全没有开销,每个实例都必须嵌入其删除器,因此将其作为类型的一部分是自然而然的事情。


顺便问一下,类型擦除的删除器是如何被调用的? - WorldSEnder
@WorldSEnder:这取决于实现方式,但通常的方法是将具体的删除器封装到一个模板类中,该类实现(继承)用于删除T*的接口。因此,当引用计数达到零时,实现会调用虚拟方法,该调用被分派到嵌入类,嵌入类再调用具体的删除器。这与std::function的机制相同。 - Horstling
1
请注意,std::function和可能的shared_ptr的“原始”低级实现在实践中可能比基于vtable的实现更快,但很少有std库使用它们(通过谷歌搜索“最快的委托”)。 vtable实现只是最容易理解的,因为它看起来像普通的C ++。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接