Concepts / The vector Container

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.

  • Programming

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.

begin()iterator to the firstelementPosition 0redPosition 1greenPosition 2blueend()one beyond the lastelement, a marker and not astored elementpoints tostopping point follows
Where do begin() and end() point before and after elements are stored, and why does end() refer to a position past the last element?

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.

begin()first elementbegin()first elementend()one beyond the lastend()one beyond the lastpush_back()adds to the endpush_back()adds to the endsize()number of elementssize()number of elements
How do vector and list use the same operation names (begin, end, push_back, size), and what is different between them?

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.

Empty vectorsize() gives 0After push_back(red)red, size() gives 1Afterpush_back(green)red, green, size() gives 2After push_back(blue)red, green, blue, size()gives 3push_back(red)push_back(green)push_back(blue)
After each push_back() call, what does the vector hold, and how does size() change as a result?
Worked example

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.

  1. 1
    Start

    The vector holds no elements, so size() returns 0. begin() and end() refer to the same position, because there is no first element and nothing before the end marker.

  2. 2
    After push_back(red)

    red is added at the end. The vector holds red. size() returns 1. end() moves to sit one position past red.

  3. 3
    After push_back(green)

    green is added at the end, after red. The vector holds red, green. size() returns 2. end() moves past green.

  4. 4
    After 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.

Result

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?

  • 0

  • 1

  • It depends on the container type

Reveal answer
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.

Call a sharedoperationfor example push_back()Does it make sensehere?for this containerNot expected to applythe shared name does notpromise supportName shared, costdifferssame meaning, differentcostCan it be implementedefficiently?for this containerOperation applieswith the shared meaningchecknoyesnoyes
Under what conditions is it expected that a shared operation such as push_back() applies to a container, and what happens when those conditions are not met?

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 like

    Reading the value at end() expecting it to be blue, the final element of the three-element vector.

    Why it fails

    end() 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 instead

    Use 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 like

    Believing that because a list is not a vector, it must be accessed with entirely new operation names.

    Why it fails

    The standard containers share operation notation and the meanings of those operations are equivalent across containers.

    Do this instead

    Read 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 like

    Assuming an operation that is cheap on one container will be just as cheap on another container that uses the same name.

    Why it fails

    An operation applies where it makes sense and can be efficiently implemented. The name is shared, but the cost can differ.

    Do this instead

    When 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

EASY

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, and end() sits past the last one.
MEDIUM

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 and end() 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 and size() returns the number of elements, so each push_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.

Related concepts

More concepts starting with T