Maestro
Maestro

Reputation: 2562

C++ primer 5th edition: Difference between the deleter of a shared_ptr and of unique_ptr's

In "C++ Primer, 5th Edition", it is said that the type of the deleter of a shared_ptr is not known until runtime, because the deleter is not stored directly as a member, but as a pointer that can point to a deleter. The type of the deleter in a unique_ptr is known at compile-time, because it is a part of the unique_ptr itself.

So, I've made this example:

#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; }};

}
// 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.

If the deleter in UniquePtr is a pointer to a function, but that pointer is nullptr, then del(p) is undefined.

Please help me to understand this paragraph. I've implemented my shared_ptr and unique_ptr as a matter of practice only.

Upvotes: 3

Views: 140

Answers (1)

eerorika
eerorika

Reputation: 238381

in my opinion, the type of the deleter in SharedPtr is known at compile time (void(*)(T*)) but the value is not.

That is true of your SharedPtr.

That is not true of std::shared_ptr.

on the other hand, the type of the deleter in UniquePtr is really known at compile time too, but again the value may not be.

When you instantiate UniquePtr with std::function<void(T*)>, you would only know that type at compile time, but not the type of the function object that is wrapped by it.

std::unique_ptr does not use std::function deleter by default.

If the deleter in UniquePtr is a pointer to a function, but that pointer is nullptr, then del(p) is undefined.

This is true. Don't do that.

Please help me to understand this paragraph.

Just because a unique pointer can have a deleter known at compile time, doesn't mean that it has to have such deleter. A function wrapper is not nor is function pointer a compile-time deleter because they have runtime state.

Use a stateless deleter like for example std::default_delete<T> (or perhaps DebugDelete mentioned in the quote which presumably is also stateless) to get compile time benefits.

Upvotes: 4

Related Questions