Chapter 9. Dereferencing NULL Pointers

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:

  1. 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.

  2. It is by default initialized to NULL (thus following the spirit of Chapter 7).

  3. 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:

  • If you have a pointer that owns the object it points to, use a smart pointer (a reference counting pointer or scoped pointer).

  • When you have a raw pointer T* pointing to an object you do not own, use the template class Ptr<T> instead.

  • For a const pointer (i.e., const T*) use Ptr<const T>.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.19.243