In OpenCog, a Handle is a reference a particular Atom. In C++ code, a Handle is a smart pointer pointing to the actual C++ atom that it references; thus, all Atom methods can be called by using the operator-> on the Handle.
The below describes how Handles actually work in the current atomspace. User's don't need to know any of this to use handles; this info is for maintainers.
In the current implementation, handles are 'smart pointers'. When you create a handle, the use count on some atom increases. When you destroy a handle the use-count decreases. If the use count on an atom goes to zero, the atom is deleted. When you assign, the use-count on the old atom decreases, the use-count on the new atom increases. If you assign an atom to itself, nothing happens.
The handle is a struct, with one members: the actual pointer to the actual atom. You can view these explicitly. so:
Handle h; Atom* ap = h.operator->(); logger().info("The pointer is %p\n", ap);
A handle with a null pointer is the 'invalid handle'.
Handles use the C++ AtomPtr class to perform reference counting and memory management. Recall that reference counting is mathematically "dual" to garbage collection. Here, "dual" in the sense of category theory, a reversal of arrows. See David F. Bacon, Perry Cheng, V.T. Rajan, "A Unified Theory of Garbage Collection" OOPSLA’04, Oct. 24-28, 2004.
AtomPtr itself is a std::shared_ptr<Atom> and a shared_pointer is a class. so AtomPtr::get() returns the actual pointer, but AtomPtr()::use_count() returns the use count. The use count should be between 1 and about 20, maybe even a little more if its a popular atom. It shouldn't be zero, negative, or 3 billion. See std::shared_pointer for details. For example:
Handle h = ...; long int cnt = h.AtomPtr().use_count(); printf("use count is %d\n", cnt);
All of this works corrently in a multi-threaded, multi-atomspace environment. The AtomSpaceAyncUTest creates a few dozen threads and bangs on various atomspaces with various usage scenarios to test this stuff.
Note, however, that the atomic thread-safety of shared-pointers is subtle. See boost documentation and the cppwisdom "shared_ptr is almost thread safe" article. It affects how shared pointers are used for TruthValues and AttentionValues, or any kind of mutable data, although Handles are not affected, because Handles are not mutable.
There still are a few buggy/unhandled areas, having to do with out-of-order atom space deletion. If you delete an atomspace that has children, weird stuff will probably happen, culminating in a crash.