The vector Container
The standard containers share operation notation, so the same name is written the same way across containers such as vector and list.
Carrying Skills from One Container to Another
Suppose you already know how to add an element to the end of one kind of container, and now you need to use a different kind. Do you have to learn a completely new vocabulary for the second one, or does the vocabulary carry over? The standard library answers this question in a specific way, and the answer shapes how you read almost any container code you meet.
In this article, the vector container is the running example. A vector and a list are two standard containers that hold elements. The point to learn is that the same operation names appear on both, and the same names carry the same meanings. Once you can trace what the four basic operations begin(), end(), push_back(), and size() do to a vector, you will be able to read the same calls on other containers without first checking which container you are looking at.
Tracing begin() and end() Positions
Start with an empty vector and then store three elements in it: red, green, and blue. The two operations begin() and end() are best read together. begin() gives an iterator to the first element. end() gives an iterator to the position one beyond the last element. Together they mark the boundaries of the whole sequence: the first position you can visit, and the position where a walk through the elements stops.
Read the diagram from left to right. The begin() position lands on the first stored element, red. The end() position sits after blue, the last stored element, and it does not hold a value of its own. That is why end() is described as one-beyond-the-last rather than as the last element. Because it sits past the final element, it works as a stopping point for a walk through the container: you move forward from begin() and stop when you reach end().
Shared Notation and Shared Meaning
The standard containers are built so that their basic operations look alike when you write them down. A call that works on one container is written with the same name and the same shape of syntax on another, so you can read the code without first checking which container you are looking at. The notation is deliberately shared.
Sharing a notation is only half of the design. The meaning of each shared operation is also equivalent across containers. An operation named for finding the start of a sequence means the start of the sequence whether the container underneath is a vector, a list, or another container type. What differs between containers lies in how each one stores its elements, not in what the basic operations are supposed to mean.
Read the two columns row by row. Each row holds one operation name, and the meaning in each row is the same in both columns. The column headings are the only things that change. This is the practical benefit of the design: knowing what begin() means on a vector tells you what begin() means on a list.
Watching push_back() and size() Change a Container
push_back() adds an element to the end of a container, and size() returns the number of elements. The two work together: each push_back() call grows the contents by one element at the end, and a following size() call reports the new count. The next diagram traces three calls on an initially empty vector.
Tracing a short sequence of appends
Start with an empty vector. Apply push_back(red), then push_back(green), then push_back(blue). After each call, state what the vector holds, where end() now sits, and what size() returns.
- 1Start
The vector holds no elements, so
size()returns 0.begin()andend()refer to the same position, because there is no first element and nothing before the end marker. - 2After push_back(red)
red is added at the end. The vector holds red.
size()returns 1.end()moves to sit one position past red. - 3After push_back(green)
green is added at the end, after red. The vector holds red, green.
size()returns 2.end()moves past green. - 4After push_back(blue)
blue is added at the end. The vector holds red, green, blue.
size()returns 3.begin()still points to red, the first element, because the new element went to the end.
After three push_back() calls the vector holds three elements in insertion order, size() returns 3, begin() points to red, and end() sits one position past blue.
What do you think happens?
You call push_back() once on an empty vector. What does size() return immediately afterward?
Reveal answer
1
push_back() adds one element to the end, and size() returns the number of elements. After one element is added, the count is 1. The shared meaning holds for vector and list alike.
When a Shared Operation Applies
Uniformity has conditions. An operation applies where it makes sense and can be efficiently implemented. A shared name therefore does not guarantee identical cost everywhere. The same name written on two containers means the same thing, but the operation may be easy on one container and expensive on another, or it may not be expected to apply at all.
Read the flowchart as a checklist. First ask whether the operation makes sense for the container you are using. If it does not, the shared name gives you no reason to expect it to apply. If it does, ask whether it can be implemented efficiently there. Only when both answers are yes is the operation expected to apply with its shared meaning. A yes on the first question and a no on the second still leaves you with the same name and meaning, but with a different cost.
Mistakes Learners Make with Shared Operations
Common mistake Treating end() as the last element
What it looks likeReading the value at end() expecting it to be blue, the final element of the three-element vector.
Why it failsend()is a position one beyond the last element. It is a marker used as a stopping point for a walk through the container, not a stored element.Do this insteadUse end() only to detect when a walk has finished. Read the last element through the position before end().
Common mistake Assuming a different container needs a different vocabulary
What it looks likeBelieving that because a list is not a vector, it must be accessed with entirely new operation names.
Why it failsThe standard containers share operation notation and the meanings of those operations are equivalent across containers.
Do this insteadRead begin(), end(), push_back(), and size() the same way on any standard container, then check the conditions for each operation.
Common mistake Assuming a shared name guarantees identical cost
What it looks likeAssuming an operation that is cheap on one container will be just as cheap on another container that uses the same name.
Why it failsAn operation applies where it makes sense and can be efficiently implemented. The name is shared, but the cost can differ.
Do this insteadWhen you move to a new container, check whether the operation is efficient there, not only whether the name is familiar.
Common mistake Expecting size() to change before push_back() is called
Practice Tracing a Container
Start with an empty vector. Apply push_back(7), then push_back(3), then push_back(9). After these calls, state the contents of the vector in order, the value of size(), which element begin() points to, and where end() sits relative to the stored elements.
Hints
- Each
push_back()adds the new element after all existing elements. size()counts the elements currently stored, not the number of calls made.begin()points to the first stored element, andend()sits past the last one.
A colleague says that because a list and a vector both have begin() and end(), any code that works on one will give the same cost on the other. Explain why this is not guaranteed, using the idea that an operation applies where it makes sense and can be efficiently implemented.
Hints
- Separate the question of meaning from the question of cost.
- Ask what the source of the difference is: the operation's meaning or how the container stores its elements.
Summary of the Shared Container Design
The vector container shows the design of the standard containers in a concrete form. Its operations are written with the same names as those of other containers, and those names carry equivalent meanings. begin() and end() mark the first element and the position one beyond the last, push_back() grows the contents at the end, and size() reports how many elements are stored. Each of these operations is expected to apply where it makes sense and can be efficiently implemented, which means that a shared name does not promise identical cost everywhere.
At a glance
- The standard containers share operation notation, so the same names are written the same way across containers such as vector and list.
begin()gives an iterator to the first element andend()gives an iterator to the position one beyond the last element, which is a stopping marker and not a stored element.push_back()adds an element to the end of a container andsize()returns the number of elements, so eachpush_back()call changes the contents and the count.- A shared operation applies where it makes sense and can be efficiently implemented, so a shared name does not guarantee identical cost on every container.