handle Users Guide

The handle is the pre-eminent “smart pointer” for reference counting in C++.

Related Documents

Overview

See intro

Contents

Five Flavors of Smart Pointer

const_handle
handle
cow
See Cow Usage
const_baro
baro

Requirements for T

The can_handle class

Get_lifetime_object

Cloning cows

Threading and Housekeeping Issues

Handling

A handle (of any flavor) must1 only point to a dynamically-allocated object, since the handle will call delete on it when the reference count hits zero. Obviously, using the address of a stack-based or global variable is a bad idea.

A freshly-constructed object, upon returning from new, should have an original reference count of zero. Ideally, the first and only thing you do with that pointer is to give it to a handle. That increments the reference count, and now the object is part of the system and should not be freed directly.

handle<C> h (new C);  // normal way to create a handled object
class D : public B, public C { // …

void foo (handle<D>&);

/*virtual*/ void D::bar()
 {
 handle<D> self (this);
 foo (self);
 // …
 }

void baz (handle<C>& h)
 {
 h->bar();
 }

Ideally, you would use handles only and not complicate things by using raw pointers to the same object as well. But there is one case where you canít get away from it: The this pointer! It may be necessary for a member function to recover the handle when it only has the raw pointer. This feature is called “Lost & Found”.

The listing on the left shows why you canít avoid the problem. Even if “everything” is written using handles and you never pass raw pointers around, you still have this. Here bar is called on a handle, and the body of bar is not passed the handle—only the raw pointer.

So it is necessary to construct a handle around the pointer. But this is not a newly-created object; it is an object that is already known to smart pointers and has a reference count that is not zero. In this case, the handle constructor will deal with it correctly, and the library will understand that self is the same object as h.

To make things even more interesting, notice that the complete D object does not have the same address as the C base class subobject. Even though h and self have different declared types, and contain different addresses, the library understands that they are the same object.

In general, handles of different declared types can be cast or implicitly converted in exactly the same way as raw pointers to those types. It works with multiple inheritance, virtual inheritance, polymorphic objects or simple structures. Basically, there is very little you canít do with handles.

Threading and Thread Safety

The handle library is specifically designed to use with threads. The reference counting, both owning count and non-owning borrower count, is not only thread-safe but non-blocking2. Different threads may create, destroy, and copy handles to the same object, at the same time, and the library reliably determines the last owner that goes away. Handing object lifetimes with multiple threads is the main reason for designing this library.

Suppose thread A uses a handle h1, and thread B uses a handle h2, both pointing to the same underlying object. When thread A is done with it, it lets h1 go out of scope. When thread B is done with it, it lets h2 go out of scope. Assuming there are no other handles to the object besides these two, you can be certain that the correct thread—the last one to be finished with it—will destroy the object.

When it comes to using the object being handled, that is beyond the scope of the handle library. If thread A calls h1->foo() and at the same time thread B calls h2->foo(), the handles take care of their part without any sweat. But it is the implementation of foo that determines if the same object can be used simultainiously on two threads. The handles manage the shared objectís lifetime only, and does not have anything to do with the thread safety of the object being handled.

Two threads can each have their own separate handle to the same underlying object. But two threads should not use the same handle itself. If thread A and B both referred to a global h3, and thread A called h3->foo() at the same time that thread B called h3=0, you get undefined behavior.

The only things you do to modify handle objects themselves (as opposed to dereferencing them and modifying the underlying object) are construct, destruct, and assign.

When one thread is constructing an object, no other thread should know about it let alone be using it. Likewise, if one thread is destructing an object then no other thread should be using it. This is generally true in C++ and is not a special rule for the handle classes. Finally, if one thread is assigning to a handle, no other thread should be using it. Again, that is normal C++ behavior. But, it happens to be about the only thing you can do to get the reference count messed up, short of knowingly dangerous constructs like memcpy.

A cow (copy on write) handle has another subtle feature, though. A multiple ownership is essentially a lazy or delayed copy. When the cow is dereferenced, the actual copy is made. This means that the innocent-looking operator-> is a modifying operation, just like assignment! This is surprising and unintuative, and as of Public Build 8 (July 2006) the library deals with this transparantly. Two threads that call cow1->foo() at the same time, on a shared cow1 variable, with a pending lazy copy, will work itself out.

Casting — handle_cast

baro — Non-Owning References

Footnotes

A handle must point to a dynamically-allocated object, since the handle will call delete on it when the reference count hits zero.

That is the only reason why you shouldnít make handles to static or local variables. The shared empty vararray, essentially a singleton, is indeed static. It is never deleted because the reference count is artificially increased. See make_the_empty in data_t.h.


Handles are non-blocking.

The cow handle may need to wait if multiple threads trigger the delayed-copying of the same handle (not the same object, but the same handle variable) at the same time. See notes at make_unique.