Concepts / Pointers and References to Vector Elements

Pointers and References to Vector Elements

Capacity is the memory allocated to a vector and is reported by capacity(); size counts the elements actually stored, and capacity is never smaller than size.

  • Programming

Why Adding Elements Feels Like Magic

When you add elements to a vector one at a time with push_back(), it can look as though the vector simply stretches to make room. Underneath, the vector is managing a block of memory. The question of how much memory it has set aside is separate from the question of how many elements it currently holds. This article is about that second question. Once you can tell the two apart, the behavior of growing a vector stops looking like magic.

The mechanisms that make growing a vector work are two names you should keep in mind: capacity(), which reports how much room the vector has, and reserve(), which makes room for more elements. The rest of this article shows how these two cooperate with push_back().

What do you think happens?

Before reading on: if the vector allocated exactly enough room for each new element as it arrived, what would every push_back() cost?

Reveal answer
Answer:

Every push_back() would force a new allocation and a copy of everything already stored.

Adding a thousand elements that way would copy the existing elements again and again. The standard vector avoids this by allocating more room than it strictly needs at the moment.

Size Versus Capacity

Definition

Capacity is the memory allocated to a vector and is reported by capacity(). Size counts the elements actually stored. Capacity is never smaller than size.

Think of the vector's memory as a row of slots. Size tells you how many slots hold real elements, counting from the front. Capacity tells you how many slots the vector has set aside in total. The difference between them is the reserved space that is still unused. That unused space is what lets the next few push_back() calls be cheap.

The rule that capacity is never smaller than size follows from what the two numbers mean. Size counts elements that are stored, and those elements must sit inside the block the vector has allocated. So the block can never hold fewer slots than the number of elements placed in it.

Tracing Capacity as Elements Are Added

The source describes a growth rule for this model: an empty vector starts with capacity 8, and the capacity doubles after that. Using only that rule, we can trace what happens when we call push_back() repeatedly on an empty vector. The table below is a generated trace built from that rule.

Worked example

Tracing push_back() on an empty vector

Starting from an empty vector, follow size and capacity through 17 push_back() calls using the rule that capacity starts at 8 and doubles when room runs out.

  1. 1
    Start empty

    Size is 0 and the vector has no stored elements yet. Under the growth rule, the first allocation gives capacity 8.

  2. 2
    Calls 1 through 8

    Each call finds room in the existing block, since size plus 1 never exceeds capacity 8. Size rises from 1 to 8, and capacity stays at 8. These calls are cheap because they place the new element into existing space.

  3. 3
    Call 9

    Before storing the ninth element, the check finds that capacity 8 is below size plus 1 (which is 9). Reserve is called, the capacity doubles to 16, and the existing elements are moved into the new block. Then the ninth element is stored. Size is 9 and capacity is 16.

  4. 4
    Calls 10 through 16

    The block of 16 slots has room for these elements, so no reallocation happens. Size climbs to 16 while capacity stays at 16.

  5. 5
    Call 17

    Capacity 16 is below size plus 1 (which is 17), so the capacity doubles to 32 and the elements are moved again. Size is 17 and capacity is 32.

Result

Reallocations happen only at calls 1, 9, and 17 in this trace. Capacity jumps at those calls, while the other calls just fill existing room.

Notice that the pattern of cheap calls punctuated by occasional expensive ones continues as the vector grows. Because the capacity doubles, the expensive calls become rarer as the number of elements increases. Each reallocation pays for the room that will serve many later calls.

How push_back() Decides Whether to Allocate

The standard library's push_back() can be understood in terms of two operations: capacity() and reserve(). Its logic has a fixed order. First, push_back() asks whether there is room for one more element. Only if there is not does it call reserve() to make more. Then it places the new value into the next free slot. The check guards the whole operation, so the expensive path runs only when capacity is insufficient.

Rule

When this condition is true, there is no room for the new element, so reserve() is called. When it is false, the element goes straight into existing space. This is why most push_back() calls are cheap. The check itself does not allocate anything; it only compares two numbers.

What Happens to Saved Pointers and References

Reallocation is where pointers and references to elements become a concern. Suppose you save a pointer to an element, then a push_back() forces the vector to allocate a bigger block. The elements are moved into that new block. The saved pointer still records the old location, and that location no longer holds the vector's elements. The source's description of the move, that the old block is no longer where the vector's elements live, is the reason this matters.

Mistakes Beginners Make With Size and Capacity

  • Common mistake

    Treating capacity() as the number of elements in the vector

    What it looks like

    A vector with three elements and capacity 8 reports capacity() as 8, so a loop bounded by capacity() would visit five slots that hold no elements.

    Why it fails

    Capacity counts allocated slots, while size counts the elements actually stored. Capacity can be larger than size.

    Do this instead

    Use size() to count elements, and use capacity() only to learn how much room is reserved.

  • Common mistake

    Assuming reserve() adds elements

    What it looks like

    Calling reserve(20) on an empty vector and then reading its first element.

    Why it fails

    reserve() does not change size. It only makes room, so the vector still holds no elements.

    Do this instead

    Use push_back() or resize-style operations to add elements, and use reserve() only to prepare space.

  • Common mistake

    Expecting a saved pointer to follow its element after growth

    What it looks like

    Storing a pointer to element 0, then calling push_back() until the vector reallocates, and then writing through the old pointer.

    Why it fails

    After reallocation the elements live in a new block, and the saved pointer still refers to the old location.

    Do this instead

    Re-obtain pointers or references after operations that may reallocate, or avoid holding them across growth.

When Calling reserve() Pays Off

Because push_back() already grows the vector by doubling, calling reserve() is not required for correctness. It is useful when you know roughly how many elements you will add. Reserving that room up front means the allocation and move steps happen once, instead of at several growth points along the way.

Reserving is unnecessary when the vector will hold only a few elements, because the doubling rule already keeps reallocations rare. It is worth doing when the element count is known in advance and the cost of repeated moves would matter.

Practice: Predicting Capacity

EASY

A vector starts empty. Using the growth rule (first capacity 8, then doubling), predict the size and capacity after 9 push_back() calls, and state which call triggered a reallocation.

Hints
  • Check what capacity the vector has before each push_back() once size reaches 8.
  • Compare capacity with size plus 1 before storing the ninth element.
MEDIUM

A program is about to add 40 elements to an empty vector whose final size is known in advance. Explain, in terms of size, capacity, and reallocation, whether calling reserve(40) first changes the final contents, and how many reallocations it can avoid compared with the doubling rule.

Hints
  • Under the doubling rule, capacity goes 8, 16, 32, 64. Count which of those are reached while adding 40 elements.
  • reserve() does not change size.

Key Takeaways

  • Size counts the elements actually stored; capacity is the memory allocated to the vector, reported by capacity(), and is never smaller than size.
  • push_back() checks whether capacity is below size plus 1 and calls reserve() only when room is short, so most calls are cheap.
  • Under the source's growth rule, capacity starts at 8 for an empty vector and doubles thereafter, so reallocation happens only occasionally.
  • reserve() allocates only when needed and moves existing elements into the new block, but it never changes size.
  • Reallocation moves elements, so pointers and references saved before growth may no longer refer to the vector's elements.

Related concepts

More concepts starting with P