Pete Becker is a software developer at Dinkumware Ltd., where he works on standard library implementation and documentation for C, C++, and Java. He is Project Editor for the C++ Standard, and for several years has written a column for C/C++ Users Journal. He is currently writing a book about TR1. He can be contacted at [email protected].
In the previous two columns, we looked at the new TR1 template class shared_ptr, which implements a reference-counted smart pointer. Code that traffics in objects of type shared_ptr<Ty> can be much simpler than similar code that uses ordinary pointers because the controlled resource will be released when the last of those objects is destroyed, without any explicit bookkeeping in the application. The code is also more robust, because the resource will not be released while there are any shared_ptr objects in existence that own the resource. Thus, using shared_ptr objects correctly guarantees that a resource will be released when it is no longer needed, but not before.
This rigid control of the lifetime of a resource is usually just what's needed. Sometimes, however, an application needs a pointer that doesn't affect the lifetime of the resource it points to. An ordinary pointer can do this, but not safely. There's no direct way to tell whether the resource that an ordinary pointer points to still exists, so mixing ordinary pointers with shared_ptr objects increases the risk that the application will try to use a resource that has been released. To reduce this risk, TR1 provides a complement to shared_ptr in the template class weak_ptr.
Sometimes member functions return a pointer or reference to the object that they were applied to, to support chaining of operations. If that object was reached through a shared_ptr object, the returned pointer or reference can be left dangling when the object itself is destroyed. There's no easy way to get from the this pointer of a resource back to the shared_ptr object that controls the resource, so the TR1 library provides a template class, enable_shared_from_this, to make it easier for a member function to return a shared_ptr object rather than the this pointer.
This month, we'll look at both of those templates. Listing 1 provides a synopsis of the relevant parts of the TR1 header <memory>.
Terminology
We say that a shared_ptr object "owns" a resource if it was constructed with a pointer to that resource, if it was constructed from a shared_ptr object that owns that resource, if it was constructed from a weak_ptr object that points to that resource, or if ownership of that resource was assigned to it, with operator= or by calling either of its member functions reset or swap.
Further, a weak_ptr object "points to" a resource if it was constructed from a shared_ptr object that owns that resource, if it was constructed from a weak_ptr object that points to that resource, or if that resource was assigned to it with operator= or with swap [1].
An "empty shared_ptr" object does not own any resources. An "empty weak_ptr" object does not point to any resources.
A weak_ptr object has "expired" if the reference count for the resource that it points to is zero. This occurs when the weak_ptr object is empty, or if it's not empty, when the last shared_ptr object that owns the resource that the weak_ptr object points to has been destroyed [2].
The Template Class weak_ptr
Listing 2 is a synopsis of the template class weak_ptr. Most of its members require very little explanation. The default constructor constructs an empty weak_ptr object. The other constructors construct an object that points to the resource (if any) that their argument owns or points to. After an assignment, the weak_ptr object points to the resource (if any) that the object on the right-hand side of the assignment operator owns or points to. After a call to the member function reset, the weak_ptr object is empty. The member function use_count returns the number of shared_ptr objects that own the resource that *this points to [3].
Using the Controlled Resource
You probably noticed that the template class weak_ptr doesn't have any member functions to get to the controlled resource. Instead, you have to create a shared_ptr object from the weak_ptr object and use the shared_ptr object to get to the resource. That generally simplifies code that needs to get to the controlled resource, because once you have a shared_ptr object, you don't have to worry about whether the weak_ptr object has expired.
There are two ways to get a shared_ptr object from a weak_ptr object. You can construct the shared_ptr object, passing the weak_ptr object as the constructor's argument, or you can call the member function lock. The main difference between the two is how they handle an attempt to create a shared_ptr object from a weak_ptr object that has expired.
The template class shared_ptr<Ty> has a template constructor that takes a reference to a weak_ptr<Other>. Just as with the shared_ptr constructors that we looked at before, this constructor is only valid if a pointer of type Other* is convertible to a pointer of type Ty*. The constructor throws an exception object of type bad_weak_ptr if the weak_ptr argument has expired. Otherwise, it constructs an object that owns the resource that the weak_ptr points to.
The member function weak_ptr<Ty>::lock returns a shared_ptr<Ty> object that owns the resource that the weak_ptr object points to. If the weak_ptr object has expired, it returns an empty shared_ptr. Listing 3 shows the use of the constructor and this member function.
Comparing weak_ptr Objects
We saw last time that you can compare shared_ptr objects for relative order using operator<. You can do the same for weak_ptr objects. This makes it possible to use these types of pointer objects as the key type in an associative container. For example, Listing 4 shows the use of weak_ptr objects to associate a string object with an object of an arbitrary type [4, 5]. The typedef name_list is a synonym for the type map<weak_ptr<void>, string>. The various shared_ptr types created in the main function are all converted to weak_ptr<void> when they are stored as keys in the map and when they are passed as arguments to the function map::find. Note that the call to sp1.reset() releases the resource that sp1 manages. If the code had used a shared_ptr object as the key in the map instead of a weak_ptr, the shared_ptr object in the map would still hold the managed resource. (Try it.)
Using weak_ptr Objects
Use weak_ptr objects whenever you need access to a resource through a pointer that can outlive the resource. With ordinary pointers, you can't tell if the resource that the pointer points to has been freed; with weak_ptr objects you can, using either of the member functions expired() or lock(), or by attempting to construct a shared_ptr object. (The first two support an in-channel test for failure; the third throws an exception if it fails). Listing 5 (available at http://www.cuj.com/ code/) shows a data cache that uses weak_ptr objects to provide fast access to data objects that are currently in use and to fall back on slower database lookup for objects that are not. The caching code is a little tricky, so if you write code like this, you have to think very carefully about the lifetime of the various objects. In particular, the code following the comment "Look up ID that's not in cache" should not be simplified. It's tempting to rewrite it as
w = lookup(id_num); return prospect(w);
but that won't work. The shared_ptr object returned by lookup will be destroyed at the end of the statement, and since there are no other shared_ptr objects that own the new resource, the resource will be released. As written, the auto object p owns the resource. It won't be destroyed until after the return statement, at which point the returned object also owns the resource, so the resource won't be released.
Getting shared_ptr Objects In Member Functions
In an earlier column, we saw that code like this won't work correctly:
int *ip = new int; shared_ptr<int> sp1(ip); shared_ptr<int> sp2(ip);
Neither of the two shared_ptr objects knows about the other, so both will try to release the resource when they are destroyed. That usually leads to problems. Similarly, if a member function needs a shared_ptr object that owns the object that it's being called on, it can't just create an object on the fly:
struct S { shared_ptr<S> dangerous() { return shared_ptr<S>(this); // don't do this! } }; int main() { shared_ptr<S> sp1(new S); shared_ptr<S> sp2 = sp->dangerous(); return 0; }
This code has the same problem as the earlier example, although in a more subtle form. When it is constructed, the shared_ptr object sp1 owns the newly allocated resource. The code inside the member function S::dangerous doesn't know about that shared_ptr object, so the shared_ptr object that it returns is distinct from sp1. Copying the new shared_ptr object to sp2 doesn't help; when sp2 goes out of scope, it will release the resource, and when sp1 goes out of scope, it will release the resource again.
The way to avoid this problem is to use the class template enable_shared_from_this [6]. The template takes one template type argument, which is the name of the class that defines the managed resource. That class must, in turn, be derived publicly from the template; like this:
struct S : enable_shared_from_this<S> { shared_ptr<S> dangerous() { return shared_from_this(); } }; int main() { shared_ptr<S> sp1(new S); shared_ptr<S> sp2 = sp->dangerous(); // not dangerous return 0; }
The member function S::dangerous is no longer dangerous. When you do this, keep in mind that the object you call shared_from_this on must be owned by a shared_ptr object. This won't work:
int main() { S *p = new S; shared_ptr<S> sp2 = p->dangerous(); // don't do this }
Listing 6 (available at http://www.cuj.com/code/) has a synopsis of the template class enable_shared_from_this. The protected members provide ordinary construction and copy semantics, while preventing creation of standalone enable_shared_from_this objects. The two public member functions return shared_ptr objects with appropriate const qualifications [7].
Next Time
We're finished with additions to the header <memory>. Next time, we'll start looking at additions to the header <functional>, which consists of the template classes reference_wrapper, mem_fn, function, and bind. These templates can all act as wrappers around callable types, making them more uniform and easier to use.
References
- The TR1 specification uses the term "shares ownership" when a shared_ptr object owns a resource and when a weak_ptr object points to a resource. I've always felt that owning something implies the ability to destroy it, so I prefer to use two terms rather than one when talking about these relationships.
- Thus, a weak_ptr object that does not point to a valid resource is said to have expired, regardless of whether the resource was invalid from the start, as is the case with an empty object, or the resource was released through the ordinary operation of shared_ptr objects that own the resource.
- This implies that use_count returns 0 when a weak_ptr object has expired.
- This code is based on an example by Peter Dimov, who did quite a bit of the work on the specification of shared_ptr and weak_ptr. For more examples, see http://www.boost.org/libs/smart_ptr/index.html.
- If this example leads you to consider using map<weak_ptr<void>, type_info*> as a way of recovering type information from a heterogeneous container, you should first ask yourself why you're throwing the type information away. If you don't throw it away, you don't need a way to figure out what it used to be.
- That's an annoyingly long name, but you won't have to use it very often.
- If you're interested, the template enable_shared_from_this holds a weak_ptr object that points to the derived object. There's a chicken-and-egg problem, though, about how to initialize that weak_ptr object when there is no corresponding shared_ptr object. The implementation trick is that the constructors for shared_ptr know about enable_shared_from_this, and set the weak_ptr object during construction of a shared_ptr object that owns a resource that has enable_shared_from_this as a public base class.
CUJ