It is "typically stored" in whatever location is necessary for the design of an object. Intrusive smart pointers require the T
they're used with to provide the storage for the reference count. That's what makes them "intrusive"; they intrude on the object.
The design you outlined specified "can take an arbitrary object." Therefore, an intrusive design is off the table.
Since many instances of the smart pointer will have to have access to the same reference count object, that reference count must be independent of any one instance. And since it must also be independent of T
, it therefore must be an object whose lifetime is independent of both T
and any smart pointer instance that references it.
So the smart pointer, upon claiming ownership of a T
, must also create the reference count object to manage it. Typically, this is done by heap allocating such an object. Copies of the smart pointer also get a pointer to the reference count.
This is also why it is illegal to have two different std::shared_ptr
constructors claim ownership of the same T*
. You can copy from a shared_ptr
that already owns the T*
, but you cannot just pass the T*
itself directly to the constructor. Because T
has no access to the reference count, shared_ptr
's constructor would not know that someone else owns it, so it would create a second reference count block.