CS 70

A Simple Program Using std::vector

Like our last program, this one reads in a list of numbers from std::cin, and then calculates a few statistics about them. But where the previous version used a primitive array, this version uses std::vector.

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<double> values;

    while (true) {
        double value;
        std::cin >> value;
        if (!std::cin.good()) {
            break;
        }
        values.push_back(value);
    }

    double total = 0.0;
    for (auto p = values.begin(); p != values.end(); ++p) {
        total += *p;
    }

    std::cout << "Read " << values.size() << " values" 
              << ", total " << total
              << ", average " << total/values.size() << std::endl;

    std::sort(values.begin(),values.end());
    std::cout << "Min is " << values.front()
              << ", max is " << values.back()
              << ", median is " << values[values.size()/2] << std::endl;

    return 0;
}

The animated GIF below shows us compiling and using this code.

List a few things you notice that have changed in the code compared to last time, and what you think it might mean.

What's Changed?

Most of the code looks almost the same. But there are some differences.

The Declaration is Different

We used to write

double values[100];

which created an array with 100 default-initialized doubles (i.e., holding elements with indeterminate values).

Now we've written

std::vector<double> values;

and we get a vector of size zero.

If we had wanted 100 value-initialized doubles (all nicely set to 0.0), we could have written

std::vector<double> values(100);

Notice that we have to use parentheses (()) here, as curly braces ({}) can be used to provide initial values for the elements; for example, we could write

std::vector<int> favorites{42, 54, 7};

to create a vector holding the three int values 42, 54, and 7.

The type is specified in angle brackets (<>) because std::vector is generic: you can create a std:vector of any type you like by specifying what type you want in the angle brackets. In C++ parlance, it's a class template, but we'll discuss templates in more depth (including writing your own!) in a future lesson.

A std::vector Knows How Big It Is

You might also notice that the variable numValues has vanished. A std::vector knows how big it is, and we can find that out using the size() member function.

To avoid resizing too often, a vector often has some empty space to fill before it has to resize and copy all the data. We can find out the size of the primitive array used behind-the-scenes by calling the capacity() member function. If we know we'll need the array to be a certain size, we can call reserve(n) to make the compiler set aside a specific capacity, reducing the need to resize and copy and shrink_to_fit() to throw away spare space if we know an array won't grow any further. But you can usually just ignore those details, exactly as you would ignore them in Python.

  • Python speaking

    Actually, in Python you don't choose to ignore how array resizing works behind the scenes, you have to ignore it, because there's no mechanism provided to control it.

A std::vector Has a push_back(value) Function

We can add new elements to the end of a std::vector using its push_back function. Calling push_back increases the size of the array to accommodate the new elements (and may cause the entire contents of the array to be reallocated to a new location and copied if there isn't sufficient capacity in the underlying primitive array).

We can also remove the end element using pop_back(). The pop_back() member function does not return the element that was removed. Instead, you can always get at the back element using .back() (and the frontmost element using .front). These access functions return a reference that lets you modify the element, so you can say values.back() = 7.3 to change the last element to 7.3.

  • Duck speaking

    What if I try to push something that isn't a double?

  • LHS Cow speaking

    If it's a type that can be promoted or converted to a double, it will be. Otherwise, you'll get a compiler error.

  • Hedgehog speaking

    So not undefined behavior, then. I'm so relieved.

  • LHS Cow speaking

    It's not the wild west. This is an error the compiler can catch.

A std::vector Has begin() and end()

As you might expect, std::vector supports the classic array-style loop from the beginning of the array to past-the-end.

A std::vector Has operator[]

As you might expect, std::vector supports the classic array-element access using square brackets ([]), so we can get specific elements with values[0], values[59], and so on.

A std::vector Is a Proper Value, It Doesn't Decay to a Pointer

You can't easily copy or assign a whole primitive array (without resorting to a for loop), but std::vector behaves like any other well-behaved object and has a copy constructor and an assignment operator.

A std::vector Has Even More Functionality

  • Pig speaking

    MORE! Oh, yeah!

We can insert new elements into the array wherever we like, and also erase existing elements. These member functions have to shift elements around in the array, so they aren't very efficient, but they can still be useful. We can also efficiently swap two vectors in constant time.

  • Horse speaking

    Hold your horses! How on earth can we efficiently `swap`` two vectors in constant time? What if they have lots of elements or are different sizes? Did you make a mistake?

  • LHS Cow speaking

    No, it's true. We'll see how it could be true soon enough.

Plus, we can use any of the C++-array algorithms that work on start/past-the-end values.

A std::vector Always Uses Dynamic Memory Allocation.

The program above may not seem like it's calling new and delete, but behind the scenes, inside the code for std::vector, it is.

  • Bjarne speaking

    Technically, std::vector uses an allocator object, and you can control all the details of how its dynamic-memory allocation works.

  • LHS Cow speaking

    True, but it defaults to new and delete (via std::allocator), and we won't worry here about all the extra control that's available.

A std::vector<double> object probably contains exactly three data members: a pointer and two size_t values. Describe them, say what the pointer points to, and list what the two size_t values are holding.

The three data members that the std::vector<double> object probably contains are

  • A pointer to the array of values on the heap (i.e., a double* pointer pointing to the first element of the array).
  • A size_t holding the number of elements actually in the vector.
  • A size_t holding the capacity of the vector (i.e., including blank spaces that don't have elements in them yet).

  • RHS Cow speaking

    But the implementation of std::vector is private, so we can only speculate.

  • LHS Cow speaking

    It's probably a pretty good speculation, though.

(When logged in, completion status appears here.)