One of the most frequent reasons for program crashes (a.k.a.
core dumps under Unix) is an attempt to dereference a NULL pointer. As we
saw in the previous chapter, both smart pointers discussed there—the
RefCountPtr
and the ScopedPtr
—have run-time diagnostics for that.
However, not every pointer is a smart pointer that has ownership of some
object. To diagnose an attempt to dereference a pointer that does not have
ownership of an object, I’ll introduce here a “semi-smart” pointer that does
not delete the object it points to. Let’s take a look at the public portion
of it in the file scpp_ptr.hpp:
// Template pointer, does not take ownership of an object. template <typename T> class Ptr { public: explicit Ptr(T* p=NULL) : ptr_(p) { } T* Get() const { return ptr_; } Ptr<T>& operator=(T* p) { ptr_ = p; return *this; } T* operator->() const { SCPP_TEST_ASSERT(ptr_ != NULL, "Attempt to use operator -> on NULL pointer."); return ptr_; } T& operator* () const { SCPP_TEST_ASSERT(ptr_ != NULL, "Attempt to use operator * on NULL pointer."); return *ptr_; }
Despite the presence of operator=
, this is not an
assignment operator that would tell the compiler what to do when we try to
assign one Ptr<T>
to another.
The assignment operator for this class, if we had writthen one, would be
declared as:
Ptr<T>& operator=(const Ptr<T>& that);
Note that the operator=
declared in the
preceding class has a different signature: it includes a raw pointer
p
on the right side. Therefore, this
class leaves it up to the compiler to create both the copy constructor and
the assignment operator of the Ptr<T>
. Because both
the copy constructor and assignment operators for the Ptr<T>
class are
allowed, you are free to copy these pointers, return them from functions,
and so on.
At this point you might ask: if we are advised to use Ptr<T>
instead of
T*
, what should we use for
a const
T*
pointer? The answer is
easy: Ptr<const T>
. Suppose you
have a class:
class MyClass { public: explicit MyClass(int id) : id_(id) {} int GetId() const { return id_; } void SetId(int id) { id_ = id; } private: int id_; };
If you want to create a semi-smart pointer that behaves like const MyClass*
, all you have to do is
write:
scpp::Ptr<const MyClass> p(new MyClass(1));
cout << "Id = " << p->GetId() << endl; // Compiles and runs.
p->SetId(666); // Does not compile!
Note that an attempt to call a non-const
function on this
pointer does not compile, which means that it correctly reproduces the
behavior of a const
pointer.
The Ptr<T>
template
pointer has the following features:
It does not take ownership of the object it points to, and should be used as a replacement for a raw pointer in the same situation.
It is by default initialized to NULL (thus following the spirit of Chapter 7).
It offers run-time diagnostics of an attempt to dereference itself when it is NULL.
Rules for this chapter to catch attempts to dereference a NULL pointer:
3.144.19.243