C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 16:
16.1 Does delete p delete the pointer p, or the pointed-to-data *p?
16.2 Is it safe to delete the same pointer twice?
16.3 Can I free() pointers allocated with new? Can I delete pointers allocated with malloc()?
16.4 Benefits of new over malloc()?
16.5 Can I use realloc() on pointers allocated via new?
16.6 Checking for NULL after p = new Fred()?
16.7 How can I convince my (older) compiler to automatically check new to see if it returns NULL?
16.8 Checking for NULL before delete p?
16.9 What are the two steps that happen when I say delete p?
16.10 Does p = new Fred() leak memory if the ctor throws an exception?
16.11 How do I allocate / unallocate an array of things?
16.12 What if I forget the [] when deleteing an array allocated via new T[n]?
16.13 Can I drop the [] when deleteing an array of some built-in type (char, int, etc)?
16.14 After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?
16.15 Is it legal (and moral) for a member function to say delete this?
16.16 How do I allocate multidimensional arrays using new?
16.17 How to simplify the Matrix code from the previous FAQ?
16.18 How to make the Matrix class generic?
16.19 What's another way to build a Matrix template?
16.20 Does C++ have arrays whose length can be specified at run-time?
16.21 Allocating all objects via new, not local/global/static?
16.22 How do I do simple reference counting?
16.23 How do I provide reference counting with copy-on-write semantics?
16.24 How do I provide reference counting with copy-on-write semantics for a hierarchy of classes?
16.25 Preventing people from subverting the reference counting mechanism?
16.26 Can I use a garbage collector in C++?
16.27 What are the two kinds of garbage collectors for C++?
16.28 Where can I get more info on garbage collectors for C++?
[16.17] But the previous FAQ's code is SOOOO tricky and error prone! Isn't there a simpler way?

Yep.

The reason the code in the previous FAQ was so tricky and error prone was that it used pointers, and we know that pointers and arrays are evil. The solution is to encapsulate your pointers in a class that has a safe and simple interface. For example, we can define a Matrix class that handles a rectangular matrix so our user code will be vastly simplified when compared to the the rectangular matrix code from the previous FAQ:

// The code for class Matrix is shown below...
void someFunction(Fred& fred);

void manipulateArray(unsigned nrows, unsigned ncols)
{
  Matrix matrix(nrows, ncols);   // Construct a Matrix called matrix

  for (unsigned i = 0; i < nrows; ++i) {
    for (unsigned j = 0; j < ncols; ++j) {
      // Here's the way you access the (i,j) element:
      someFunction( matrix(i,j) );

      // You can safely "return" without any special delete code:
      if (today == "Tuesday" && moon.isFull())
        return;     // Quit early on Tuesdays when the moon is full
    }
  }

  // No explicit delete code at the end of the function either
}
The main thing to notice is the lack of clean-up code. For example, there aren't any delete statements in the above code, yet there will be no memory leaks, assuming only that the Matrix destructor does its job correctly.

Here's the Matrix code that makes the above possible:

class Matrix {
public:
  Matrix(unsigned nrows, unsigned ncols);
  // Throws a BadSize object if either size is zero
  class BadSize { };

  // Based on the Law Of The Big Three:
 ~Matrix();
  Matrix(Matrix const& m);
  Matrix& operator= (Matrix const& m);

  // Access methods to get the (i,j) element:
  Fred&       operator() (unsigned i, unsigned j);         subscript operators often come in pairs
  Fred const& operator() (unsigned i, unsigned j) const;   subscript operators often come in pairs
  // These throw a BoundsViolation object if i or j is too big
  class BoundsViolation { };

private:
  unsigned nrows_, ncols_;
  Fred* data_;
};

inline Fred& Matrix::operator() (unsigned row, unsigned col)
{
  if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
  return data_[row*ncols_ + col];
}

inline Fred const& Matrix::operator() (unsigned row, unsigned col) const
{
  if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
  return data_[row*ncols_ + col];
}

Matrix::Matrix(unsigned nrows, unsigned ncols)
  : nrows_ (nrows)
  , ncols_ (ncols)
//, data_  <--initialized below (after the 'if/throw' statement)
{
  if (nrows == 0 || ncols == 0)
    throw BadSize();
  data_ = new Fred[nrows * ncols];
}

Matrix::~Matrix()
{
  delete[] data_;
}
Note that the above Matrix class accomplishes two things: it moves some tricky memory management code from the user code (e.g., main()) to the class, and it reduces the overall bulk of program. The latter point is important. For example, assuming Matrix is even mildly reusable, moving complexity from the users [plural] of Matrix into Matrix itself [singular] is equivalent to moving complexity from the many to the few. Anyone who has seen Star Trek 2 knows that the good of the many outweighs the good of the few... or the one.