C++ primer第5版:shared_ptr和unique_ptr的deleter之间的区别

3
在《C++ Primer, 5th Edition》中提到,shared_ptr的删除器类型直到运行时才知道,因为该删除器并没有直接作为成员存储,而是作为一个指针来指向删除器。而在 unique_ptr 中,删除器的类型在编译时已知,因为它是 unique_ptr 的一部分。
因此,我制作了以下示例:
#include <functional>

template <typename T>
struct SharedPtr
{
    void(*pDel_)(T*) = nullptr;
    T* ptr_{ new T{} };
    ~SharedPtr(){ pDel_ ? pDel_(ptr_) : delete ptr_; }
};

template <typename T, typename D = std::function<void(T*)>>
struct UniquePtr
{
    D pDel_;
    T* ptr_{ nullptr };
    UniquePtr(T* p = nullptr, D del = D{}) :
        ptr_(p), pDel_(del){}
    ~UniquePtr(){ pDel_(ptr_); }
};

int main()
{
    SharedPtr<int> spi{};
    cout << *spi.ptr_ << endl;
    UniquePtr<std::string> upd{new std::string("Helo!"),
        [](std::string* p){std::cout << "freeing memory...\n"; delete p; }};

}
  • 我认为,在 SharedPtr 中,删除器的类型在编译时是已知的 (void(*)(T*)),但值不是。

  • 另一方面,在 UniquePtr 中,删除器的类型也确实在编译时已知,但其值可能不是。

  • 因此,这本书说:

// del bound at compile time; direct call to the deleter is instantiated
del(p);   // no run-time overhead
The type of del is either the default deleter type or a user-supplied type. It doesn’t
matter; either way the code that will be executed is known at compile time. Indeed, if
the deleter is something like our DebugDelete class (§ 16.1.4, p. 672) this call might
even be inlined at compile time.
By binding the deleter at compile time, unique_ptr avoids the run-time cost of an
indirect call to its deleter. By binding the deleter at run time, shared_ptr makes it
easier for users to override the deleter.

如果UniquePtr中的删除器是函数指针,但该指针为nullptr,则del(p)的行为未定义。
请帮我理解这段话。我已经实现了我的shared_ptr和unique_ptr,仅仅是为了练习。
1个回答

4
在我的看法中,SharedPtr中的deleter类型在编译时已知(void(*)(T*)),但其值则未知。
这对于你的SharedPtr来说是正确的。
但对于std::shared_ptr来说则不然。
另一方面,UniquePtr中的deleter类型在编译时真的已知,但其值可能不知道。
当你用std::function<void(T*)>实例化UniquePtr时,你只能在编译时知道该类型,但不知道包装它的函数对象的类型。
std::unique_ptr默认不使用std::function deleter。
如果UniquePtr中的deleter是一个指向函数的指针,但该指针为nullptr,则del(p)是未定义的。
这是正确的。不要这样做。
仅仅因为独占指针可以在编译时具有已知的deleter,并不意味着它必须有这样的deleter。函数包装器也不是编译时deleter,因为它们具有运行时状态。
使用无状态deleter,例如std::default_delete<T>(或者可能是引用中提到的DebugDelete,它也很可能是无状态的)以获得编译时的优势。

你的意思是如果我这样做:std::unique_ptr<int, std::function<void(int*)>> upi(new int{7}, [](int* p ){delete p;});,它是否与std::shared_ptr的运行时开销相同? - Maestro
1
@Maestro 关于删除器的编译时特性:基本上是这样。共享指针除此之外还有其他开销,例如原子引用计数器等。 - eerorika
好的。非常感谢您。您能推荐一些关于智能指针实现的好参考资料吗? - Maestro
1
@Maestro 我建议你了解一下 _类型擦除_。它被应用于 std::functionstd::shared_ptr 的删除器中。 - Daniel Langr
1
@Maestro 你可以阅读源代码获取详细信息,Boost文档中也可能会有一些内容介绍,但主要是关于如何使用它们的。 - eerorika
显示剩余2条评论

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