A Dynamic Array Using a Class (Part 3)¶
Getting Rid of Magic Values¶
In the previous int_vec
examples (part 1, part 2),
there were a couple of examples of magic numbers, or more generally
magic values.
A magic number is a number (or some other non-numeric value, like a
string
) that is not explained. For example, 10 is a magic number in these
two constructors:
class int_vec {
// ...
public:
int_vec()
: capacity(10), arr(new int[capacity]), size(0)
{ }
int_vec(int sz, int fill_value)
: capacity(10), size(sz)
{
// ...
}
// ...
}; // class int_vec
You cannot tell from this code what 10 means. Would it be okay to change it to 9, or 11, or 100? Or -5?
Another problem is that there is now easy way for the user of int_vec
to
know what the default capacity is. So it would be helpful if there was a
getter (or equivalent) for the default capacity.
A good solution to both of these problems is to use a static class member. For example:
class int_vec {
// ...
public:
static const initial_capacity = 10;
int_vec()
: capacity(initial_capacity), arr(new int[capacity]), size(0)
{ }
int_vec(int sz, int fill_value)
: capacity(initial_capacity), size(sz)
{
// ...
}
// ...
}; // class int_vec
initial_capacity
is a static
variable, which means all int_vec
objects share a single copy of it. Individual int_vec
objects do not
have their own personal copies of initial_capacity
in the way that they
have their own copies of capacity
, arr
, and size
. Thus, this saves
a little bit of memory, especially if you are using many int_vec
objects.
We’ve also declared initial_capacity
to be const
because we never want
a user of int_vec
to change initial_capacity
.
Here’s an example of how to access the initial capacity of an int_vec
:
int main() {
cout << "initial capacity of an int_vec: " << int_vec::initial_capacity
<< "\n";
// ...
}
Notice that we don’t need to construct an int_vec
object in order to
access initial_capacity
. For static class members, the ::
notation is
used to access them, i.e. int_vec::initial_capacity
. In C++, the
.
-notation only works with objects, and so we use ::
with a class.
::
-notation is often a good way to talk about methods. For example,
suppose we write a class called Product
and a class called Truck
, and
they both have a method named print()
(and they print different things, of
course). The expression Truck::print()
refers to method in Truck
, and
Product::print()
is the one in Product
..
Exercises¶
In
append
method, the capacity of the underlying array is doubled if necessary. The 2 used in the doubling calculation is an example of a magic number. So, replace the 2 inappend
with a suitably named static member variable as was done with the initial capacity.Another magic number occurs in
operator==
: the “10” in the call toresize
. Fix this by creating a new static member as done above.The
int_vec::print
method andoperator<<
both have a couple of magic values: the{
and}
used to mark the start and end of the vector when printed, and", "
is used as a separator.Create static variables (as done above) to get rid of these magic numbers in both
int_vec::print
method andoperator<<
.Suppose you decide to allow the user the option of
int_vec
to change the start/end characters of a printedint_vec
, and also to change the separator", "
.How would you do this?
Summing and Sorting¶
Finally, lets add a method that returns the sum of the numbers in an
int_vec
like this:
int_vec v;
v.append(4);
v.append(2);
v.append(3);
cout << v.sum(); // prints 9
Here are three different implementations of sum
:
// ...
#include <numeric>
// ...
class int_vec {
// ...
public:
// ...
int sum1() const {
int result = 0;
for(int i = 0; i < size(); i++) {
result += (*this)[i];
}
return result;
}
// trick: using a reference to avoid writing (*this)[]
int sum2() const {
int result = 0;
const int_vec& v = *this;
for(int i = 0; i < size(); i++) {
result += v[i];
}
return result;
}
// trick: use std::accumulate (from <numeric>)
int sum3() const {
return accumulate(arr, arr + sz, 0);
}
}; // class int_vec
In sum3()
, accumulate
is a standard C++ function that, in this case,
takes three input: a pointer (arr
) to the start of the array, a pointer
(arr + sz
) to one past the last element of the array, and finally the
initial value of the sum (i.e. 0). The advantage of sum3
is that it is
short, easy to read, most likely error free (functions in the standard library
tend to be very extensively by users of C++), and very efficient.