共享指针和具有自定义删除器的唯一指针之间的语法差异背后的任何原因

Posted

技术标签:

【中文标题】共享指针和具有自定义删除器的唯一指针之间的语法差异背后的任何原因【英文标题】:Any reason behind the syntax difference between shared pointers and unique pointers with custom deleters 【发布时间】:2018-10-24 08:36:43 【问题描述】:

虽然 C++11 中的共享指针和唯一指针都允许用户定义删除器,但它们有显着的语法差异,如下面的迷你示例所示:

#include "pch.h"
#include <memory>
class factory

public:
    static factory * create() 
        return new factory();
    
    static void destroy(factory*    fp) noexcept
        delete fp;
    
    factory(const factory &) = delete;
    factory& operator= (const factory &) = delete;
private:
    char * p_;
    factory() 
    p_ = new char[100];
    
    ~factory() 
        delete[] p_;
    
;

int main()

    typedef void(*fp)(factory*);

    auto del = [](factory * p) noexcept 
        factory::destroy(p);
    ;

    std::shared_ptr<factory> fsptr1(factory::create(), del);
    std::shared_ptr<factory> fsptr2(factory::create(), del);
    //notice the syntax is different
    std::unique_ptr<factory, fp> ufsptr1(factory::create(), del);
    std::unique_ptr<factory, decltype(del)> ufsptr2(factory::create(), del);

    return 0;

这背后的原因是共享指针的模板类定义为

template< class T > class shared_ptr;

唯一指针的模板类定义为

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

我的问题是:这个设计决策背后是否有任何理由表明两者的语法彼此不同?我天真的想法是,如果共享指针的模板类被制作为

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

这会更有意义。一方面,它与唯一指针的情况一致,另一方面,当默认删除器格式不正确并且用户未能提供自定义删除器时,它不会实例化。

【问题讨论】:

您希望能够在shared_ptr&lt;T, Deleter1&gt;shared_ptr&lt;T, Deleter2&gt; 之间进行分配吗?使用shared_ptr&lt;T&gt; 你可以(即使删除器不同)。 一个区别是你可以声明shared_ptr的前向声明类,而unique_ptr必须看到完整的定义。 这是什么destroy静态方法?这是非常糟糕的设计......delete[] 也是如此。 @Jarod42 在 shared_ptr 与不同的删除器之间进行分配是一件好事吗? 除了@Jarod42 在shared_ptr 中,删除器“绑定”到实例。恕我直言,这很重要。否则,您可以通过分配给另一个 shared_ptr 来更改实例的删除器,这将是最后一个幸存者。那将是致命的。 【参考方案1】:

默认的std::unique_ptr 只存储一个元素,指向它保护的数据的指针。这是因为默认情况下,您希望使用尽可能少的内存。但是当您指定删除器时,您还需要存储它。所以你需要区分这两个版本。

请看这里:https://github.com/llvm-mirror/libcxx/blob/master/include/memory#L2397

存储是基于模板类型的特定类型。

但是对于std::shared_ptr,你没有这个约束,你已经有了一个计数器,你需要分配一个块来存储它。因此,您可以在分配逻辑内部而不是外部,在 API 级别做出删除选择。

请看这里:https://github.com/llvm-mirror/libcxx/blob/master/include/memory#L3592

compressed_pair 不在智能指针本身,而是在分配的块中。

【讨论】:

我不太理解你的推理。当 one 指定删除器时,它们都需要存储它。 @JohnZ.Li:EBO(空基优化)可能适用。 是的,但是对于shared_ptr,您不会像唯一指针那样存储删除器,而是在指针和计数器所在的位置分配一个块。所以如果你指定一个删除器,你可以分配更多来存储它。这可以在带有删除器的构造函数中完成。对于 unique_ptr,您必须在两个实现之间静态切换。 @MatthieuBrucher 你的意思是缓存效率? 谢谢,我终于明白了。

以上是关于共享指针和具有自定义删除器的唯一指针之间的语法差异背后的任何原因的主要内容,如果未能解决你的问题,请参考以下文章

通过 Boost Python 在 C++ 对象之间传递共享指针的 Segfault

为啥 C++ 共享指针的行为不像迭代器的标准指针?

指向重载静态成员的函数指针 - 在 unique_ptr 中用作自定义删除器

fork(),C 中的共享内存和指针

C ++中的指针和引用之间是不是存在任何开销差异[重复]

共享指针递归删除递归数据结构,堆栈溢出