Usually, unique_ptr and shared_ptr just call delete on their internal pointers, whenever they ought to destroy the object they maintain. In this section, we constructed a class which can neither be allocated the C++ way using x = new Foo{123} nor can it be destructed with delete x directly.
The Foo::create_foo function just returns a plain raw pointer to a newly constructed Foo instance, so this causes no further problems because smart pointers work with raw pointers anyway.
The problem we had to deal with is that we need to teach unique_ptr and shared_ptr how to destruct an object if the default way is not the right one.
In that regard, both the smart pointer types differ a little bit. In order to define a custom deleter for unique_ptr, we have to alter its type. Because the type signature of the Foo deleter is void Foo::destroy_foo(Foo*);, the type of the unique_ptr maintaining a Foo instance must be unique_ptr<Foo, void (*)(Foo*)>. Now, it can hold a function pointer to destroy_foo, which we provide it as a second constructor parameter in our make_unique_foo function.
If giving unique_ptr a custom deleter function forces us to change its type, why were we able to do the same with shared_ptr without changing its type? The only thing we had to do there was giving shared_ptr a second constructor parameter, and that's it. Why can't it be as easy for unique_ptr as it is for shared_ptr?
The reason why it is so simple to just provide shared_ptr some kind of callable deleter object without altering the shared pointer's type lies in the nature of shared pointers, which maintain a control block. The control block of shared pointers is an object with virtual functions. This means that the control block of a standard shared pointer compared with the type of a control block of a shared pointer with a custom deleter is different! When we want a unique pointer to use a custom deleter, then this changes the type of the unique pointer. When we want a shared pointer to use a custom deleter, then this changes the type of the internal control block, which is invisible to us because this difference is hidden behind a virtual function interface.
It would be possible to do the same trick with unique pointers, but then, this would imply a certain runtime overhead on them. This is not what we want because unique pointers promise to be completely overhead free at runtime.