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

  1. 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 in append with a suitably named static member variable as was done with the initial capacity.

  2. Another magic number occurs in operator==: the “10” in the call to resize. Fix this by creating a new static member as done above.

  3. The int_vec::print method and operator<< 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.

    1. Create static variables (as done above) to get rid of these magic numbers in both int_vec::print method and operator<<.

    2. Suppose you decide to allow the user the option of int_vec to change the start/end characters of a printed int_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.