Feb. 4, 2010, 3:19 p.m.
posted by nogood
Item 42. Smart PointersWe C++ programmers are a loyal bunch. Whenever we're faced with a situation that requires a feature the language doesn't support, we don't abandon C++ to flirt with some other language; we just extend C++ to support the feature in which we're interested. For instance, it's often the case that you'll need something that behaves like a pointer, but a built-in pointer type just doesn't do the job. In those cases, a C++ programmer will use a "smart pointer." (See also Function Objects [18, 63] for similar observations about function pointers.) A smart pointer is a class type that is tricked up to look and act like a pointer but that provides additional capability beyond that provided by a built-in pointer. Generally, a smart pointer uses the capabilities provided by a class's constructors, destructor, and copy operations to control access to or keep track of what it points to in a way that a built-in pointer cannot. All smart pointers overload the -> and * operators so that they can be used with standard pointer syntax. (Some rare specimens even go so far as to overload the ->* operator; see Pointers to Class Members Are Not Pointers [15, 53].) Other smart pointers (in particular, smart pointers used as STL iterators) overload other pointer operators, like ++, --, +, -, +=, -=, and [] (see Pointer Arithmetic [44, 149]). Smart pointers are often implemented as class templates so that they may refer to different types of objects. Here's a very simple smart pointer template that performs a check that it's not null before use:
template <typename T>
class CheckedPtr {
public:
explicit CheckedPtr( T *p ) : p_( p ) {}
~CheckedPtr() { delete p_; }
T *operator ->() { return get(); }
T &operator *() { return *get(); }
private:
T *p_; // what we're pointing to
T *get() { // check ptr before returning it
if( !p_ )
throw NullCheckedPointer();
return p_;
}
CheckedPtr( const CheckedPtr & );
CheckedPtr &operator =( const CheckedPtr & );
};
Use of a smart pointer should be straightforward, mimicking the use of a built-in pointer: CheckedPtr<Shape> s( new Circle ); s->draw(); // same as (s.operator ->())->draw() The key to this façade is the overloaded operator ->;. The -> operator must be overloaded as a member and has a rather unusual property in that it is not "consumed" when it is called. In other words, when we write s->draw(), the compiler recognizes that s is not a pointer but a class object with an overloaded operator -> (that is, that s is a smart pointer). This results in a call to the member overloaded operator, which returns (in this case) a Shape * built-in pointer. This pointer is then used to call Shape's draw function. If you write this out longhand, you'll get the following challenging expression: (s.operator ->())->draw(), which contains two uses of the -> operator, one overloaded, one built in. Smart pointers also typically overload operator * as well as operator -> so that they may be used to refer to nonclass types. CheckedPtr<int> ip = new int; *ip = 12; // same as ip.operator *() = 12 (*s).draw(); // use on ptr to class, too Smart pointers are used pervasively in C++ programming, from resource handles (see RAII [40, 139] and auto_ptr Is Unusual [43, 147]) to STL iterators, to reference counting pointers, to wrappers around pointers to member functions, and on and on. Semper fidelis. |
- Comment