The New C++:Smart(er) Pointers
Last time [1], I gave a survey of the first batch of suggested library extensions that were considered at the October 2001 C++ standards meeting in Redmond, Washington, USA. This time, as promised, we'll take a closer look at one of the proposed facilities — smart pointers, which were discussed again at the following April 2002 standards meeting in Curaçao, in the Netherlands Antilles.
And Then There Was One
In today's Standard C++, there's only one smart pointer:
std::auto_ptr
.
Considering that it's such a tiny feature,
auto_ptr
sure has received copious attention. The main reason for the attention is that attention is deserved, both positively and negatively. On the one hand, auto_ptr
is very useful for specific situations and those uses deserve to be described; see the discussion in Items 19 and 37 of Exceptional C++ [2], Item 10 of More Effective C++ [3], and my article "Using auto_ptr
Effectively," available online [4]. On the other hand, auto_ptr
can also be moderately suspicious to highly dangerous in other situations, such as the minefield of trying to put auto_ptr
s into standard containers likevector
and map
; see for example Item 21 of More Exceptional C++ [5] and Item 28 of More Effective C++ [3]. (On the gripping hand, as Niven and Pournelle might say, auto_ptr
relies on some fairly obscure language trickery to make it deliberately not work in some dangerous cases. Fortunately this trickery gets less press — the details are not for the faint of heart. Suffice it to say that auto_ptr
is designed with some sleight of hand that's intended to make it deliberately break when used with, say, standard containers.)
So today's Standard C++ has exactly one smart pointer:
auto_ptr
. That's it. So it must be all you need, right? Not a bit of it. There's more to this story.
It's actually a real shame that
auto_ptr
is the only standard smart pointer, for at least two reasons. First, auto_ptr
doesn't do all the useful things that smart pointers might be good for; there are many good uses of smart pointers that poor auto_ptr
was never designed to support, and shouldn't, and doesn't. Second, auto_ptr
can be a dangerous snake that turns and bites you on the hand if you do try to use it in some of those other ways.
The first piece of good news is that many good and useful smart pointers are available and were available even before
auto_ptr
. The only problem is that they weren't, and aren't, standard. That's somewhere between disappointing and annoying, depending on how portable your code needs to be — standard features are of course generally the most portable.
The second piece of good news is that those alternative smart pointers are now strong candidates for inclusion into the Standard. (Some could have been in the original Standard; see [1]. But better late than never.)
If One Is Good, Six Are Better... (?)
The Boost library ships with five additional smart pointers [6]. All of these smart pointer templates take a single type parameter specifying the type of the object to be held. Briefly, here they are:
scoped_ptr
: a non-copyable smart pointer intended to be used as an auto (stack) object. When ascoped_ptr
goes out of scope and is destroyed, it will automatically delete the single object it points to. Arguably,scoped_ptr
is whatauto_ptr
ought to have been in the first place, way back whenauto_ptr
was first meant to be really an "auto" pointer. Butscoped_ptr
doesn't support the modernauto_ptr
's important additional usefulness for sources and sinks (described in [4]).scoped_array
: likescoped_ptr
, but owns an array instead of a single object.shared_ptr
: a reference-counted smart pointer intended to be used to share "handles" to the pointed-at object. When ashared_ptr
goes out of scope and is destroyed, if it is the lastshared_ptr
pointing at the owned object, it will automatically delete the owned object — a classic case of "last one out, turn off the lights." Note thatshared_ptr
does supportauto_ptr
's important use for sources and sinks. More importantly, it can be safely used in a container, which forauto_ptr
s is not allowed and highly dangerous.shared_array
: likeshared_ptr
, but owns an array instead of a single object.weak_ptr
: to be used in conjunction with ashared_ptr
. If you have an object that's already managed by one or moreshared_ptr
s, you can createweak_ptr
s to it too. Now, theshared_ptr
s still follow the "last one out, turn off the lights" policy; the lastshared_ptr
will delete the owned object even if there are stillweak_ptr
s to it. How isweak_ptr
then any better than just a bald pointer to the object? Because theshared_ptr
that turns off the lights will also check to see if anyweak_ptr
s still point to that object and set them to null if they do. That's a level of safety you don't get with plain old raw pointers.
Let me repeat some advice from last time, because it's worth repeating: if you know nothing else about Boost, know about
shared_ptr
. It's especially valuable if you ever want to have a container of pointers, because what you almost always really want is a container ofshared_ptr
s. For one thing, they'll be managed for you and will get cleaned up correctly, and they'll avoid the usual pitfalls of using mutating STL algorithms on containers of bald pointers. For another, they are not auto_ptr
s, which is a Good Thing because auto_ptr
s should never be put into STL containers.
The Boost approach is to have lots of special-purpose smart pointer types, one for each kind of smart pointer behavior. In this model, a smart pointer template is quite simple:
1
2
3
4
5
| template < typename T> class scoped_ptr; template < typename T> class scoped_array; template < typename T> class shared_ptr; template < typename T> class shared_array; template < typename T> class weak_ptr; |
Only one template parameter is needed, to specify the type of the owned object. It is both an advantage and a disadvantage that these little class templates can all have very different interfaces if they want to. On the one hand, it allows customization, say when a member function might make sense for one but not another smart pointer. But, on the other hand, it also means that it's easy to get inconsistent interfaces, especially when people start extending the facility by providing more smart pointers of their own, both within and outside the Boost library.
If Six Are Good, Infinity Is Better... (?!)
Although Boost chooses to follow the model of designing special-purpose smart pointer templates, we can ask: "Why stop there?" In fact, some very bright language designers, notably Andrei Alexandrescu in Modern C++ Design [7], have proposed that a "one size fits all" über-pointer may be an achievable dream, using policy-based design.
Policy-based design takes the view that certain details, such as checking policies and ownership policies, can be abstracted out into their own classes, which are then supplied as template parameters to configure or customize the basic smart pointer. Thus Loki's
SmartPtr
template looks something like this:
1
2
3
4
5
6
7
8
9
| template < typename T, template < class > class OwnershipPolicy = RefCounted, class ConversionPolicy = DisallowConversion, template < class > class CheckingPolicy = AssertCheck, template < class > class StoragePolicy = DefaultSPStorage > class SmartPtr; |
Note that we still have the obligatory template parameter
T
that describes the type of the pointed-at object. But we also get policies that govern much of the behavior of the smart pointer. The policies may look cumbersome to type out, but they are defaulted for those who don't want or need to specify them; the default Loki::SmartPtr
is a lot like aboost::shared_ptr
. In fact, SmartPtr
covers all that the four basic Boost smart pointers can do, and more. (An equivalent of Boost's weak_ptr
could also be provided to work with a partial specialization of SmartPtr
corresponding to shared_ptr
.)
Indeed, Loki's
SmartPtr
is so much a superset of Boost's smart pointers that we would love to just create synonyms. In Standard C++, the following code won't work because it relies on a feature that was frankly just overlooked, namely typedef
templates (for some existing discussion, see [8]; I'll be writing more about this up-and-coming feature in the future). Barring major surprises, such as the Earth suddenly deciding to break out of its usual orbit and head for Mars on a holiday, typedef
templates will be in the next C++ Standard, C++0x (see[9]). If we had this feature, we could write the following synonym:
1
2
3
4
5
6
7
8
9
10
11
12
13
| namespace boost { template < typename T> // typedef templates aren't standard typedef Loki::SmartPtr // yet, but let's overlook that for now... < T, // note, T still varies RefCounted, // but everything else is fixed DisallowConversion, NoCheck, DefaultSPStorage > shared_ptr; } |
So that the usage would simply be, as usual:
1
| shared_ptr< int > a; // simple -- no fuss, no muss! |
Similar synonyms could be created for
shared_array
, scoped_ptr
, scoped_array
, and evenstd::auto_ptr
. Such simple names for common configurations would make all the tedious template parameters go away for those common cases. We can't do that today, alas, but once typedef
templates become commonly available we will be able to do this and more.SmartPtr
really then describes an unlimited family of smart pointers having a consistent interface. Note that because Loki's SmartPtr
inherits publicly from its template parameter types, the interface does not have to be identical across the whole family; the policies could add some visible functionality too. Loki already comes with several predefined policies allowing hundreds of combinations. But you can add your own policies, such as for managing COM and CORBA distributed objects and other advances uses, so the size of the SmartPtr
family really is effectively unlimited.Summary
The standards committee is still considering these options and welcomes other smart pointer submissions. In particular, there is ongoing discussion, with different points of view, about whether the "one size fits all" customizable policy-based design is the way to go, or whether just a few special-purpose smart pointer templates are needed. Want to weigh in? Start discussion on the Boost reflector [10] or on the
comp.std.c++
newsgroup.
Next time: more news from the April 2002 standards meeting in Curaçao. Stay tuned.
References
[1] H. Sutter. "The New C++: The Group of Seven — Extensions under Consideration for the C++ Standard Library," C/C++ Users Journal C++ Experts Forum, April 2002, <www.cuj.com/experts/2004/sutter.htm>.
[4] H. Sutter. "Using
auto_ptr
Effectively," C/C++ Users Journal, October 1999, available online at <www.gotw.ca/publications/using_auto_ptr_effectively.htm>.
[8] H. Sutter. "Guru of the Week #79: Template Typedef," available online at <www.gotw.ca/gotw/079.htm>.
[9] H. Sutter. "The New C++," C/C++ Users Journal C++ Experts Forum, February 2002, <www.cuj.com/experts/2002/sutter.htm>.
About the Author
Herb Sutter (www.gotw.ca) is secretary of the ISO/ANSI C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (www.gotw.ca/cpp_seminar). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.
No hay comentarios:
Publicar un comentario