How do shared pointers know how many pointers point to that object? (shared_ptr, in this case)
There are at least three well-known mechanisms.
When the first shared pointer to an object is created, a separate reference count object is created and initialized to 1. When the pointer is copied, the reference count is increased; when a pointer is destroyed it is decreased. Pointer assignment increases one count and decreases another (in that order, or else self-assignment ptr=ptr
will break). If the reference count hits zero, no more pointers exist and the object is deleted.
An internal counter requires that the object pointed to has a counter field. This is usually achieved by deriving from a specific base class. In exchange, this saves a heap allocation of the reference count, and it allows repeated creation of shared pointers from raw pointers (with external counters, you'd end up with two counts for one object)
Instead of using a counter, you can keep all shared pointers to an object in a circular graph. The first pointer created points to itself. When you copy a pointer, you insert the copy in the circle. When you delete it, you remove it from the circle. But when the destroyed pointer pointed to itself, i.e. when it's the only pointer, you delete the pointed-to object.
The downside is that removing a node from a circular single-linked list is rather expensive as you have to iterate over all nodes to find the predecessor. This can be especially painful due to poor locality of reference.
The 2nd and 3rd idea can be combined: the base class can be part of that circular graph, instead of containing a count. Of course, this means that the object can be deleted only when it points to itself (cycle length 1, no remaining pointers to it). Again, the advantage is that you can create smart pointers from weak pointers, but the poor performance of deleting a pointer from the chain remains an issue.
The exact graph structure for idea 3 doesn't matter too much. You could also create a binary tree structure, with the pointed-to object at the root. Again, the hard operation is removing a shared pointer node from that graph. The benefit is that if you have many pointers on many threads, growing part of the graph is not a highly contended operation.