Chapter 20: Dynamic Arrays: std::vector

Basic Usage

#include <vector>
std::vector<int> empty{}; // vector containing 0 int elements
std::vector<int> primes{ 2, 3, 5, 7 };          // vector containing 4 int elements with values 2, 3, 5, and 7
std::vector vowels { 'a', 'e', 'i', 'o', 'u' }; // vector containing 5 char elements with values 'a', 'e', 'i', 'o', and 'u'. Uses CTAD

std::vector<int> data(10); // vector containing 10 int elements, value-initialized to 0

primes.size();     // returns length as type `size_type` (alias for `std::size_t`)
std::size(primes); // C++17, returns length as type `size_type` (alias for `std::size_t`)
std::ssize(primes); // C++20, returns length as a large signed integral type
#include <vector>

struct Foo
{
    std::vector<int> v1(8); // compile error: direct initialization not allowed for member default initializers
    std::vector<int> v{ std::vector<int>(8) }; // ok
};

Accessing Elements

primes[2];
primes.at(3); // print the value of element with index 3
primes.at(9); // invalid index (throws exception)

Passing Vectors to Functions

void passByRef(const std::vector<int>& arr); // we must explicitly specify <int> here

void passByRef(const auto& arr); // C++20

template <typename T>
void passByRef(const T& arr); // will accept any type of object that has an overloaded operator[]

Copy and Move Semantics

Loops

std::size_t length { primes.size() };
for (std::size_t index{ 0 }; index < length; ++index)
{
    // Do something with primes[index]
}

Using Sign Values in Loops

Range-Based For Loops (For-Each Loop)

for (int /*auto*/ num : primes) // num same type as primes
    std::cout << num; // print the current value of num

for (const auto& word : words) // word is now a const reference

Auto vs Auto& vs Const Auto&

Using Unscoped Enumerators for Indexing

namespace Students
{
    enum Names : unsigned int // explicitly specifies the underlying type is unsigned int
    {
        kenny, // 0
        kyle, // 1
        stan, // 2
        butters, // 3
        cartman, // 4
        max_students // 5
    };
}

std::vector<int> testScores { 78, 94, 66, 77, 14 };
testScores[Students::stan] = 76; // we are now updating the test score belonging to stan

Students::Names name { Students::stan }; // non-constexpr
testScores[name] = 76; // may trigger a sign conversion warning if Student::Names defaults to a signed underlying type, enum Names : unsigned int -> no warning

// Ensure the number of test scores is the same as the number of students
assert(std::size(testScores) == Students::max_students);

Using Scoped Enumerators for Indexing

enum class StudentNames // now an enum class
{
    kenny, // 0
    kyle, // 1
    stan, // 2
    butters, // 3
    cartman, // 4
    max_students // 5
};

std::vector<int> testScores(static_cast<int>(StudentNames::max_students)); // use static_cast

// Overload the unary + operator to convert StudentNames to the underlying type
constexpr auto operator+(StudentNames a) noexcept
{
    return static_cast<std::underlying_type_t<StudentNames>>(a);
}

std::vector<int> testScores(+StudentNames::max_students);

Dynamic Array Operations

Resizing

v.resize(5);   // resize to 5 elements larger
v.resize(3);   // resize to 3 elements smaller

The length of a vector is how many elements are “in use”. The capacity of a vector is how many elements have been allocated in memory.

v.capacity();

When a std::vector changes the amount of storage it is managing, this process is called reallocation, which typically requires every element in the array to be copied. This can be expensive.

std::vector<int> v(1000); // allocate room for 1000 elements
v.resize(0);
v.shrink_to_fit(); // now room for 0, it can be ignored by the compiler

std::vector<int> stack(5); // direct set capacity

Stack Operations

Operation Name Behavior Required? Notes
Push Put new element on top of stack Yes
Pop Remove the top element from the stack Yes May return the removed element or void
Top or Peek Get the top element on the stack Optional Does not remove item
Empty Determine if stack has no elements Optional
Size Count of how many elements are on the stack Optional
push_back() Push Put new element on top of stack Adds the element to end of vector
pop_back() Pop Remove the top element from the stack Returns void, removes element at end of vector
back() Top or Peek Get the top element on the stack Does not remove item
emplace_back() Push Alternate form of push_back() that can be more efficient Adds element to end of vector
reserve() Changes just the capacity (if necessary)
resize() Changes the length of the vector, and the capacity (if necessary)
emplace_back() Push When creating a new temporary object to add to the container, or when you need to access an explicit constructor

std::vector<bool>

std::vector<bool> is not a vector or container. Favor constexpr std::bitset, std::vector<char>, or 3rd party dynamic bitsets.