Custom std::shared_ptr implementation
Nice first post!

There are things that std::unique_ptr provides which are absent here, notably the aliasing constructor (so you can own the containing object of what's referenced) and weak pointers (so cached values don't cause unwanted object retention) but otherwise, this is pretty good.

Omissions

I had a couple of compilation errors, because it's missing #include &lt;functional&gt;.  Also, the deleter ought to have a specialization for array types (which need delete[] p; instead of delete p;).

Naming

On naming, I'd probably choose something like release() for what you've called free() - but that's probably just because of half a lifetime writing C.

Copy and swap

You mention the copy-and-swap idiom; this is how you'd use it for your assignment operator:

template&lt;typename T&gt;
SharedPointer&lt;T&gt;&amp; SharedPointer&lt;T&gt;::operator=(SharedPointer rhs)
{
    swap(rhs);
    return *this;
}


Notice that we pass rhs by value, which means we're getting the copy constructor to do all the work for us.  Having swapped, rhs has our old value, which is then destructed at the end of the function, so we don't have to duplicate the work of the constructor.

Move-assignment

Move assignment doesn't need to reset rhs - just perform the swap, and expect the moved-from value to be destructed soon:

template &lt;typename T&gt;
SharedPointer&lt;T&gt; &amp;SharedPointer&lt;T&gt;::operator=(SharedPointer &amp;&amp;rhs) noexcept
{
    swap(rhs);
    return *this;
}


Swap

The swap function doesn't need to be a friend, as it uses only the public interface of the shared pointer.

Use default arguments to simplify

You can reduce the number of constructors, like this:

SharedPointer(T *ptr = nullptr,
              const std::function&lt;void(*)&gt; &amp;del = Deleter())
    : p(ptr),
      use_c(new std::size_t(ptr!=nullptr)),
      deleter(del)
{ }


This is also true for the reset() method.

Reset should change deleter

The condition in reset() skips changing the deleter if there's no change to the pointer.  But we might be calling reset() for exactly that purpose.  Also, if a null pointer is passed as the new value, it's treated the same as a non-null pointer.  Both of these problems can be avoided by using swap() and default arguments (to re-use our constructor and destructor):

    // declaration
    void reset(T* = nullptr, const std::function&lt;void(T*)&gt;&amp; = Deleter());

template &lt;typename T&gt;
void SharedPointer&lt;T&gt;::reset(T *rhs, const std::function&lt;void(T*)&gt;&amp; del)
{
    swap(SharedPointer(rhs, del));
}