Yet another smart pointer implementation for learning
Main problem with the reference counter:

As it stands right now, your code will eventually leak or try to delete the same pointer more than once. You are keeping a diferent reference count inside each smart pointer, which is not synchronised, so smart pointers sharing the same object can have different counts for the same memory. Take this simple example, similar to your second one:

sp&lt;MyObject&gt; ptr1 = new MyObject(); // ptr1.ref_cnt = 1
sp&lt;MyObject&gt; ptr2 = ptr1; // ptr1.ref_cnt = 1, ptr2.ref_cnt = 2
// Now we have two different counts for the same object!


Once you pass the copy constructor or assignment operator, the reference count of the original pointer is left unaltered. This cannot possibly end well. That's why in the original example you've linked the author used a pointer for the reference counter as well, so that any smart pointer sharing ownership of an object can also point to the same counter. Once the owned object is to be freed, the last pointer also frees the counter.

One cleaver trick to avoid allocating the counter in a separate new call is to allocate both the object and the counter as the same block of raw bytes, then adjust the pointers and construct the object on the remaining memory. This is how some implementations of std::make_shared() work. Once you fix the counter issue, you might want to try this optimization.

Other issues with the code:


This destructor seems broken:


   ~sp() { 
     if(ptr_) 
       std::cout &lt;&lt; "~sp: " &lt;&lt; ptr_ &lt;&lt; ", ref count: " &lt;&lt; ref_cnt_ &lt;&lt; std::endl;
       --ref_cnt_;
       if(ref_cnt_ == 0)
         delete ptr_;     
   }



Didn't you forget to wrap the first if with curly braces? This seems like a case where the indenting of the code tries to tell the reader one thing, while the code does a different thing. The first if only applied to the cout call. To avoid this confusion, it is a good idea to always provide { } even for single line control statements. So this is how I believe you intended to write, since it wouldn't make sense decrementing the counter if the pointer is null:

~sp() { 
    if(ptr_) {
        std::cout &lt;&lt; "~sp: " &lt;&lt; ptr_ &lt;&lt; ", ref count: " &lt;&lt; ref_cnt_ &lt;&lt; std::endl;
        --ref_cnt_;
        if(ref_cnt_ == 0) {
            delete ptr_; 
        }
    }
}

The comparison operators that take a raw pointer should take that pointer by const T*, to make clear that they do not attempt to modify the pointed object.
You'll probably also want to provide overloads of operator * and -&gt; that are const and return const pointer and reference. Otherwise you aren't able to call those on a const sp&lt;T&gt;.
Your class still requires a lot of tuning and polishing to be anywhere near usable. If you plan on taking it a step further, I recommend reading Modern C++ Design. The book has a dedicated chapter on Smart Pointers. It is very detailed and well written and I think it is still one of the best references out there on SPs, even though it was released before C++11.