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 wrote a column for C/C++ Users Journal. He is currently writing a book about TR1. He can be contacted at [email protected].
Last month, we began talking about the new TR1 template class shared_ptr. This month we'll continue that discussion, and next month we'll finish it off with an examination of the two template classes weak_ptr and enable_shared_from_this. As a reminder, Listing 1 provides a synopsis of the relevant parts of the header <memory>, and Listing 2 provides a synopsis of the template class shared_ptr.
Using the Controlled Resource
Once you've created a shared_ptr object, you use the controlled resource in pretty much the way you'd expect. Its member operator-> returns a pointer to the controlled resource, so a shared_ptr<Ty> object acts pretty much like a pointer to Ty. The member function get also returns a pointer to the controlled resource. The member operator* returns a reference to the controlled resource; of course, this reference is only valid if the stored pointer is not null; see Listing 3.
The member functions unique and use_count give you information about the number of shared_ptr objects that own the same resource. The function unique returns true only if the object that it was called with is the sole owner of the resource. The function use_count returns the number of owners. It is intended primarily for debugging.
You can also insert a shared_ptr object into a stream; when you do that, the effect is the same as inserting the pointer returned by get, as shown in Listing 4.
The only mysterious-looking thing in shared_ptr is the operator boolean-type. That obviously illegal declaration is shorthand for an operator that returns an unspecified type that is convertible to bool when used in boolean contexts. The reason for this handwaving is that direct conversions to bool (or to any other built-in type) can cause confusion and ambiguity. So the requirement is that the returned type be convertible to bool, which makes code like 1 + ptr illegal, but preserves the meaning of code like if(ptr). This operator returns false if the shared_ptr object is empty; otherwise, it returns true; see Listing 5.
Pointer Types, shared_ptr Types, and Conversions
If you looked closely at the synopsis of shared_ptr, you may have noticed that its constructors and the reset member functions that take pointers are templates: They can take pointers to types that are different from the type argument of the shared_ptr object. Of course, that freedom isn't absolute. The rule is that the type of the pointer Other* that you pass as an argument has to be convertible to the type Ty* that the shared_ptr<Ty> holds[1]. Internally, the shared_ptr object keeps track of the actual type of the pointer that was passed to it, and when it's time to release the managed resource, it will treat it as an object of type Other, as shown in Listing 6.
Similarly, you can construct a shared_ptr <Ty> object that's a copy of a shared_ptr <Other> object if Other* can be implicitly converted to Ty*. Thus, implicit conversions for shared_ptr types mimic the implicit conversions for pointer types. This mimicry extends to the new style casts, as well. Resist the temptation to write code like this:
// BAD CODE, DON'T DO THIS: shared_ptr<base> spb(new derived); shared_ptr<derived> spd(static_cast <derived*>(spb.get());
The problem is that you now have two independent shared_ptr objects trying to manage the same resource. Your program will do bad things when they both release it. Instead, use the cast functions static_ pointer_cast, dynamic_pointer_cast, and const_pointer_cast that are defined in <memory>, as shown in Listing 7.
Deleters
The shared_ptr destructor deletes the stored pointer. If that's not what you need, you can provide a deleter at the time you create the original shared_ptr object. The deleter must be a callable object that can be called with the argument passed to the constructor. When it's time to release the resource, the deleter is called with the original object pointer, as in Listing 8.
Listing 8 also shows the template function get_deleter. You provide the template function's first type argument explicitly. If the shared_ptr object has a deleter of that type, the function returns a pointer to the deleter; if it doesn't have a deleter or if the deleter is a different type, it returns a null pointer.
Comparing shared_ptr Objects
You can compare two shared_tr<Ty> objects for equality in the usual way. Two such objects a and b are equal if a.get() == b.get(). Listing 9 shows comparisons for equality and inequality. You can also compare two shared_ptr<Ty> objects for relative order with operator<. This lets you use shared_ptr objects as keys in associative containers, as shown in Listing 10.
These comparison operators have a small quirk: Two shared_ptr objects that hold null pointers compare equal, but it's also possible that one is less than the other. That's because a < b and b < a are both required to be true only if a and b own the same resource or if they are both empty. As we saw last month, you create an empty shared_ptr object with the default constructor. Passing a null pointer to the constructor does not create an empty object, so one of the ordering comparisons must be false for a pair of objects constructed from null pointers. To see how this quirk arises in practice, take a look at Listing 11.
Exception Safety
Under the hood, when you create the first shared_ptr object that manages a particular resource, the implementation allocates a control block that holds the original pointer, the reference count [2], and a copy of the deleter, if there is one. If that allocation fails, the implementation releases the resource, just as if the reference count had gone to zero, and rethrows the exception caused by the initial allocation failure. This prevents memory leaks; this is shown in Listing 12.
Next Time
Next time we'll look at the template class weak_ptr, which holds a reference count that doesn't affect releasing of the controlled resource. That may not sound very useful, but there are good reasons for having it. We'll also look at the template class enable_shared_from_this, which enhances a derived type with a member function that returns a shared_ptr object that owns the derived object.
References
- [1] In general, this implies that the type Other must be complete, otherwise the compiler would not know that Other* is convertible to Ty*. However, if the type Ty is void (that is, you've created an object of type shared_ptr<void>) the conversion can be done even if Other is not a complete type. That's not allowed, however: TR1 requires that Other be a complete type if you're going to rely on this conversion.
- [2] Actually, there are two reference counts: one for the shared_ptr objects and one for the weak_ptr objects; we'll look at the template weak_ptr next time.
CUJ