handle, const_handle, cow class reference

The templates handle, const_handle, and cow are general purpose smart pointers. They provide reference-counted shared ownership of the referenced object, so the object is destroyed automatically when the last reference to it is dropped.

A const_handle provides read-only access to the referenced object. A regular handle provides read/write access. And a cow provides copy-on-write semantics. See the Userís Guide for more explaination.


explicit const_handle (const T* p=0)

explicit const_handle (T* p)

explicit handle (T* p=0)

explicit cow (T* p=0)

any handle

constructor, public

The primary constructors take a regular pointer as an argument, and produce a smart pointer. This constructor does increment the reference count, so the plain object should be constructed initially with a reference count of zero.

Note that the null pointer is supported, and a default constructor will create a null smart pointer.

Type Argument Constraints

The type T, the object being pointed to, must satisfy the template. Specifically, the expression Get_lifetime_object(p) must be supported. The easiest way to do this for your own types is to inherit from can_handle.

To use a cow, the type T must either have a clone member, or cow<T>::clone can be explicitly specialized.

See the Userís Guide for a more detailed discussion.

No Implicit Conversion

These constructors are explicit because implicit conversion of a raw pointer to a handle will delete it!

void foo (handle<C>);

C* p= new C;
foo (p);  if allowed, would surprise you
p->bar();  BOOM!  p has already been deleted

This is easily understood by expanding the function in-line: The parameter is a handle that is initialized with the raw pointer, setting the reference count to 1. When that parameter goes out of scope, the reference count drops to 0 and that triggers deletion. Basically, this clashes with the normal use of constructing a handle from a raw pointer and then using the handle and forgetting all about the raw pointer directly.

If you really want to construct a handle around a pointer, you have to do so explicitly, not implicitly.


const_handle (const const_handle& other)

handle (const handle& other)

cow (const cow& other)

any handle

constructor, public

The copy constructors create a new counted reference to an existing object. Note that since handle and cow are both derived from const_handle, the “isa” relationship applies. So, you can use a cow or a handle anywhere that a const_handle is declared. This means that the const_handle copy constructor can copy (regular) handles as well. However, initializing a const_handle from a cow finds an exact match below so doesnít call this form.

Threading Issues

When initializing any kind of a handle from a cow, it has to interoperate with the locking mechanism used by operator->. The copy constructor for cow does this, so may need to wait on the other thread. The other forms in this section know this is not the case so never block.


const_handle (const const_handle<U>& other)

handle (const handle<U>& other)

handle (const cow<U>& other)

cow (const cow<U>& other)

constructor, public, template

These are generalized forms of the copy constructors. If there is a conversion from U* to T*, this provides a conversion from handle<U> to handle<T>, const_handle<U> to const_handle<T>, cow<U> to cow<T>, and furthermore from cow<U> to const_handle<T> and handle<U> to const_handle<T>.

The presence of these members donít prevent the ordinary copy constructor from being auto-generated, so the normal copy constructors are also defined (above) even though these are supersets of them.

Threading Issues

When initializing any kind of a handle from a cow, it has to interoperate with the locking mechanism used by operator->. The generalized copy constructor for cow does this. The form that constructs a const_handle from a cow is provided separately (rather than allowing the normal copy constructor to handle it through inheritance) because it needs this extra overhead, while initializing a const_handle from any other kind of handle does not.


explicit handle (const baro<T>& other)

explicit const_handle (const const_baro<T>& other)

constructor, public

This form allows you to create a usable handle from a baro. See the section in the overview for more information.


~const_handle()

~handle()

~cow()

destructor, public

The destructor for a handle decrements the ownership count, and frees the handled object if this was the last handle referring to it.


handle& handle::operator= (T* other)

const_handle& const_handle::operator= (T* other)

cow& cow::operator= (T* other)

any handle

assignment, public

Assigning a raw pointer to a handle increments the reference count of the object being handled, just like the constructor does. However, being assignment it decrements the ownership count of the existing contents of the handle first.

const_handle& const_handle::operator= (const const_handle& other)

assignment, public

handle& handle::operator= (const handle& other)

cow& cow::operator= (const cow& other)

assignment, public, supplied by compiler

Because cow and handle have an “isa”relationship with const_handle, either type of non-const handle can be assigned to a const_handle; but assignment of a cow to a const_handle finds an exact match below so does not call this form. And of course one const_handle can be assigned to another. Assignment will decrement the reference count of the object being forgotten and increment it for the one being referenced.

const_handle<T>& operator= (const cow<U>& other)

Assigning any kind of cow to a const_handle needs the extra overhead of a cow on the right-hand-side, so this overloaded form is provided. That way the general form (which is a superset of this) can avoid the overhead since it knows the right side will not be a cow.

const_handle<T>& operator= (const const_handle<U>& other)

This provides assignment from any kind of const_handle (if the object types T and U are compatible) and through inheritance also provides assignment from any kind of (regular) handle.

handle<T>& operator= (const handle<U>& other)

This provides assignment from any kind of handle to another handle.

Threading Issues

Note that assigning to a handle (any flavor) is not thread-safe. That is, if one thread does h1=h2; and another thread does h1=h3; at the same time, bad things will happen. However, if you have two handles pointing to the same underlying object initially, h1a=h1b;, then one thread may perform h1a=h2; and another thread may perform h1b=h3; at the same time.

When assigning to kind of a handle from a cow, it has to interoperate with the locking mechanism used by operator->. The forms that take a cow as the right-hand-side do this. The others know they donít have to.


const T* const_handle::operator->() const

T* handle::operator->() const

T* cow::operator->() const

any handle

public

Dereferencing a handle of any flavor provides access to the referenced object. For a plain handle, read/write access is provided. For a const_handle, read-only access is provided because the return value is a const T*.

For a cow, the referenced object is cloned first if it is being shared, so the returned object is always unique.

This cannot distinguish between subsequent calling of a const or non-const member on the returned pointer, so dereferencing a cow will always ensure a unique object. If you will be only reading and donít wish to clone, call const_object instead, or assign to a temporary const_handle first.

Threading Issues

For a cow, this operator calls make_unique, so see notes there on threading issues.

For all handle flavors, the handle library itself will properly provide access to the same underlying object from multiple threads dereferencing handles at the same time. However, it does not add anything to that classís own thread safety! For example, if you have h1->foo() the use of handles doesnít change what foo does (including whether foo is thread safe) once this operator-> resolves the underlying object.


operator bool() const

any handle

public

Returns true if the pointer is not null.


bool operator!() const

any handle

public

Returns true if the pointer is null.


bool operator==(const handle_structure<T>& a, const handle_structure<T>& b)

bool operator!=(const handle_structure<T>& a, const handle_structure<T>& b)

any handle

non-member, template

const_handle<C> h1;
cow<C> h2;
// …
if (h1 == h2) {
   // …

This tests for equality of the underlying object. The == operator returns true if both handles point to the same object. The != operator is precicely the opposite of ==.

These will compare different flavors of handle (handle_structure is a base class of all the handle types), as seen to the right.

const_handle<C> h1;
const_handle<D> h2;
// …
if (h1 == h2) {
   // …

You can also have different types of underlying pointer type. As seen in the listing to the left, handles to different types can be compared. It makes exactly as much sence as comparing raw pointers to different types: there has to be a common base class or conversion operator involved.

The two kinds of flexibility can be used at the same time. You can compare different kinds of handles to different declared types. This feature is not immediatly apparent from the signature, which shows both parameters having an underlying object of the same type T. Since there is a conversion chain from any kind of handle to U to a const handle_structure<T> (navigate up the hierarchy from whatever concrete handle class to const handle_structure<U>, then use the generalized copy constructor convert a const handle_structure<U> to a temporary handle_structure<T>, which can be bound to the parameter), and the comparison only makes sence if one of the types is derived from the other (so there is an implicit conversion from either D* to C* or vice-versa but not both), the compiler figures out the unique way to make it fit the template.

There are some comparisons that are meaningful in context that the compiler wonít accept, but that is the same as with comparisons between raw pointers of different types. If the corresponding pointer comparison needed help by using explicit casts, the handle comparison will need the same help. See the Userís Guide for more details.

See also points_to.


T* cow::clone (T*)

cow

public, static

If you need to supply a clone member to type T, but cannot, an alternative is to explicitly specialize this member.

struct S;  // defined in someone else's header; not mine to alter

cow<S> h1;
cow<S> h2 (h1);
h1->foo();  // no such member S::clone.

The example above shows a situation that will not compile, giving an error while attempting to instantiate the template. Adding the clone member specialization to the cow will take care of it:

namespace classics {
template<> S* cow<S>::clone (S* original)
 {
 return new S (original);
 }
}

Note that implementing the code here, instead of in a virtual clone member of the underlying object, will not behave polymorphically. See the Userís Guide for more on cloning issues. If using “somebody elseís” class hierarchy with the cow template, this specialization will be written to call the supplied cloning mechanism that exists as part of that class.


const T* const_object() const

any handle

public

When you have a cow, as opposed to a plain handle or const_handle, dereferencing it with -> will cause the object to be cloned if it is being shared. If you want read-only access, to call const members, then it is unnecessary to copy since the idea is to copy-on-write.

For consistancy, const_object is available on any of the handle flavors. In addition to being particularly useful with cows, it could be used when writing templates that work on any flavor of handle (though you might still consider defining a local const_handle).


T* data() const

any handle

public

This returns the underlying object, without modifying the reference count. If youíre doing something a little funny so using operator-> is awkward, then use data instead.

Note that in a const_handle, the return value is a const pointer, in keeping with the meaning of a const_handle. For a cow, calling data causes a copy to be made if the data was being shared, again exactly like operator-> (see also const_object).


const lifetime* get_lifetime_object() const

any handle

public, advanced

This returns the lifetime object associated with the underlying object, for read-only access. Note that if the underlying pointer is null, the returned lifetime object is not null also, but a real lifetime object that counts references like any other. Whether or not all null handles use the same lifetime object is unspecified (it depends on the implementation of the global Get_lifetime_object overload for T.)

The implementation does some error checks to possibly catch usage errors.

This function is provided to allow unit testing, so reference counts can be examined after doing sample manipulations. It might also be used if you wanted to detect a heavily-referenced object as opposed to a lightly used one. Use is_unique if you just want to know if the handle is sharing data or not.

This function is not to be confused with the private get_Lifetime that provides read/write access, and is used within the implementation.


bool is_unique() const

any handle

public

This allows code to detect whether the handle is the only handle pointing to the underlying object, or whether additional handles are sharing the same object.

This could be used by code that can optimize for the situation. For example, when inserting into the middle of a string it is more efficient to copy the parts to the final positions of the larger destination rather than duplicating it first and then inserting; if the copy is already unique, though, it may be able to change it in-place without making another copy. In general, this allows “lazy copy” to become an optimized copy based on later context.


bool cow::make_unique() const

cow

public, advanced

This forces any pending copy to be completed, and is what is called by operator-> and data. If code desides that it needs to dis-entangle from any other shared users, without calling some member yet, it is clearer to call make_unique then to make a dummy call to data and ignore the result.

The return value is true if it did actually make a copy, and false if it didnít do anything because the data was not shared to begin with.

Threading Issues

The central point of the cow flavor of handle is that a copy of the handled object will be created only if needed, and delayed until (if ever) it is needed. The pending delayed copy makes dereferencing essentially like an assignment—it replaces the content with a pointer to a newly-created object.

If left alone, this makes multiple threads using the same handle variable run into the same thread-safety issue as assignment. It is doing the assignment, delayed from when it was actually specified. However, making operator-> subject to the same threading restrictions as assignment is unintuative and error-prone. So, as of Public Build 9, make_unique is implemented to be thread-safe if called on multiple threads at the same time for the same cow object. The case illustrated here is handled transparantly, but unlike other handle operarations one thread may have to wait for the other to complete the cloning.

prior to starting threads
cow<C> cow1 (new C);
cow<C> cow3 (cow1);
const_handle<C> ch4 (cow1);
Thread 1Thread 2Thread 3
cow1->foo();
   // operator-> calls make_unique
cow1->bar();
   // operator-> calls make_unique
cow2= cow1;
   // use on right-hand-side

In this example, an underlying object is pointed to by three handles, including cow1. Two threads each trigger copy-on-write at the same time (ignore Thread 3 for now), so each thread wants to make cow1 point to a unique copy of the original shared object. When the execution of the two calls to cow1.make_unique() overlap, one thread will do the cloning and the other will wait for it to finish. The waiting is currently done by simply calling the Win32 Sleep function with a minimal time; in the future it will use a general-purpose threading transaction class or a µ-mutex class, not yet part of Classics.

This waiting is not as bad a general-purpose blocking of threads. Thread 2 needs to wait for thread 1 to finish, but it is not waiting for thread 1 to finish its own work so thread 2 can do something else that can't be done at the same time. Rather, what thread 1 is doing is exactly what thread 2 wants done, too! Thread 2 is not blocked unable to make progress, but is delegating its completion to thread 1. Both threads do make progress. This situation cannot lead to deadlock or starvation.

Now look at Thread 3, an assignment from cow1. Again, using an object on the right-hand-side of an assignment is not normally something that either modifies the object or causes threading issues. But, it affects the reference count, so does modify the internal state of the smart pointer. The implementation guarantees that thread 3 proceeds (with a single atomic increment of the reference count) without any special knowedge of the cloneing in progress or the dance done by multiple calls to make_unique. This is true for all possible (legal) operations on the same cow object that is undergoing the call to make_unique. Only other calls to make_unique on the same cow object are affected. The cost of this feature is that cow handles are slightly larger than other types of handles, containing one additional field for use by the locking mechanism.

If all threads do their things at the same time, itís possible that Thread 1 will proceed first, making cow1 distinct from cow3 or ch4; then Thread 3 proceeds and makes cow1 non-unique again; then Thread 2 proceeds. Thread 2 makes cow1 unique again, and bar is called on a different object from foo. If the execution of the threads donít overlap their locked regions of code, this could indeed happen, just like writing sequential code to do things in that order:

C* temp1= cow1.make_unique();
cow2= cow1;
C* temp2= cow1.make_unique();
temp1->foo();
temp2->bar();

The value in temp1 is still valid because cow2 is holding a reference to the same object that cow1 used to be. But, what if Thread 3 runs for a while longer before we get back to Thread 1, and cow2 goes out of scope? Nothing good.

This is a compelling reason to guarantee the following implementation detail: If two calls to make_unique are performed on the same object with overlapping execution, both will return the same object even if it is no longer unique by the time the second call returns. That is, the above scenareo will always behave as if Thread 3 went last.

Of course, trying to execute cow1=whatever; at the same time as anything else on cow1 is ill-formed, and the implementation makes no attempt to deal with that possibility.


bool points_to (const T* p) const

any handle

public

This returns true if the handle is currently pointing to the same object as p.


typedef T Type

any handle

public

This provides a declaration for the type used to instantiate the template. Youíll need this if you use a handle in another template, and need to know what T was.