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.
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.
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.
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.
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.
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.
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.
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.
constructor, public
This form allows you to create a usable handle from a baro. See the section in the overview
for more information.
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.
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.
assignment, public
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.
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.
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.
This provides assignment from any kind of handle to another handle.
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.
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.
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.
public
Returns true if the pointer is not null.
public
Returns true if the pointer is null.
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.
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.
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).
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).
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.
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.
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.
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 1 | Thread 2 | Thread 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.
public
This returns true if the handle is currently pointing to the same object as p.
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.