|
||||
|
Section 16:
|
[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.
|
|||