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.
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
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
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.
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.
- 1Start empty
Size is 0 and the vector has no stored elements yet. Under the growth rule, the first allocation gives capacity 8.
- 2Calls 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.
- 3Call 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.
- 4Calls 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.
- 5Call 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.
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.
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 likeA 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 failsCapacity counts allocated slots, while size counts the elements actually stored. Capacity can be larger than size.
Do this insteadUse size() to count elements, and use capacity() only to learn how much room is reserved.
Common mistake Assuming reserve() adds elements
What it looks likeCalling reserve(20) on an empty vector and then reading its first element.
Why it failsreserve()does not change size. It only makes room, so the vector still holds no elements.Do this insteadUse 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 likeStoring a pointer to element 0, then calling push_back() until the vector reallocates, and then writing through the old pointer.
Why it failsAfter reallocation the elements live in a new block, and the saved pointer still refers to the old location.
Do this insteadRe-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
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.
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 callsreserve()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.