본문으로 건너뛰기

Sequential Containers

Container: A type that stores a collection of objects.

Sequential Container: A container whose elements are arranged according to their position in the sequence.

Container Adaptor: A type that provides a specialized interface on top of another container.

Common sequential containers include:

#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <string>
#include <vector>

9.1 Overview of the Sequential Containers

The sequential containers differ mainly in:

  • how elements are stored
  • how elements are accessed
  • where insertion and deletion are efficient
ContainerMain Characteristic
vectorDynamic array with fast random access
dequeFast random access and insertion at both ends
listDoubly linked list
forward_listSingly linked list
arrayFixed-size array
stringSpecialized character sequence

vector

vector: A variable-size sequence stored contiguously in memory.

std::vector<int> values{
10,
20,
30
};

std::cout << values[1];

Output:

20

vector provides fast random access.

values[2] = 100;

Insertion at the end is efficient.

values.push_back(40);

Insertion in the middle may require moving later elements.


deque

deque: A double-ended queue supporting fast insertion and removal at both ends.

std::deque<int> values{
20,
30
};

values.push_front(10);
values.push_back(40);

Result:

10 20 30 40

Unlike vector, deque efficiently supports:

push_front()
pop_front()

while also providing random access.


list

list: A doubly linked list.

std::list<int> values{
10,
20,
30
};

A list supports efficient insertion and deletion at arbitrary iterator positions.

auto iter = values.begin();

++iter;

values.insert(
iter,
15
);

Result:

10 15 20 30

A list does not provide random-access subscripting.

Invalid:

// values[2]

forward_list

forward_list: A singly linked list that supports only forward traversal.

std::forward_list<int> values{
10,
20,
30
};

It uses less overhead than a doubly linked list, but traversal is only forward.


array

array: A fixed-size standard-library container.

std::array<int, 3> values{
10,
20,
30
};

The size is part of the type:

std::array<int, 3>

and cannot change after construction.


Choosing a Sequential Container

Use vector by default unless another container has a clear advantage.

Typical choices:

Need fast random access
-> vector

Need insertion at both ends
-> deque

Need frequent middle insertion/deletion
-> list / forward_list

Need fixed size
-> array

Need character sequence
-> string

Container choice should be based on the operations the program performs most often.


9.2 Container Library Overview

Containers are class templates.

std::vector<int> numbers;
std::vector<std::string> words;

The element type is supplied as a template argument.

A container can also store another container.

std::vector<
std::vector<int>
> matrix;

Element Type Requirements

Not every container operation can be used with every possible element type.

An operation is valid only when the element type supports the operations required by that container operation.

For example, copying a container requires its elements to be copyable.


1) Iterators

Iterator: An object used to access and move through container elements.

std::vector<int> values{
10,
20,
30
};

auto iter =
values.begin();

std::cout << *iter;

Output:

10

Incrementing the iterator moves to the next element.

++iter;

std::cout << *iter;

Output:

20

Iterator Ranges

A range is normally represented by two iterators:

[begin, end)

This means:

  • begin is included
  • end is excluded

Example:

auto begin =
values.begin();

auto end =
values.end();

end() refers to one position past the last element.

It must not be dereferenced.

Wrong:

// std::cout << *values.end();

Traversing a Range

for (
auto iter = values.begin();
iter != values.end();
++iter)
{
std::cout
<< *iter
<< '\n';
}

A valid range has the property that repeatedly incrementing the first iterator eventually reaches the second.


Empty Range

A range is empty when:

begin == end

For an empty container:

std::vector<int> values;

bool empty =
values.begin()
== values.end();

Random-Access Iterator Operations

Containers such as:

  • vector
  • deque
  • array
  • string

support iterator arithmetic.

std::vector<int> values{
10,
20,
30,
40
};

auto iter =
values.begin() + 2;

std::cout << *iter;

Output:

30

list and forward_list iterators do not support this form of arithmetic.


2) Container Type Members

Containers define useful member types.

For:

std::vector<int> values;

examples include:

std::vector<int>::value_type
std::vector<int>::size_type
std::vector<int>::iterator
std::vector<int>::const_iterator

Important types:

TypeMeaning
value_typeElement type
size_typeType used for sizes
difference_typeSigned iterator-distance type
iteratorModifiable iterator
const_iteratorRead-only iterator
referenceReference to element
const_referenceReference to const element
reverse_iteratorReverse iterator

Example:

std::vector<int>::size_type size =
values.size();

Usually auto is simpler:

auto size =
values.size();

iterator vs. const_iterator

A normal iterator can modify elements.

std::vector<int> values{
1,
2,
3
};

std::vector<int>::iterator iter =
values.begin();

*iter = 100;

A const_iterator cannot modify through the iterator.

std::vector<int>::const_iterator iter =
values.cbegin();

std::cout << *iter;

But:

// *iter = 100;

is not allowed.


3) begin and end Members

Common iterator members include:

begin()
end()

cbegin()
cend()

rbegin()
rend()

crbegin()
crend()

begin() and end()

for (
auto iter = values.begin();
iter != values.end();
++iter)
{
std::cout << *iter;
}

cbegin() and cend()

These always return const iterators.

for (
auto iter = values.cbegin();
iter != values.cend();
++iter)
{
std::cout << *iter;
}

Use them when the loop should only read the elements.


Reverse Iterators

rbegin() starts at the last element.

rend() represents the position before the first element.

std::vector<int> values{
10,
20,
30
};

for (
auto iter = values.rbegin();
iter != values.rend();
++iter)
{
std::cout
<< *iter
<< ' ';
}

Output:

30 20 10

4) Defining and Initializing a Container

Containers can be initialized in several ways.


Default Construction

std::vector<int> values;

A variable-size container is initially empty.

values.empty();

returns true.


List Initialization

std::vector<int> values{
1,
2,
3,
4
};

The number of initializers determines the size.


Copy Construction

std::vector<int> source{
1,
2,
3
};

std::vector<int> destination(
source
);

destination contains copies of the elements in source.


Range Construction

A container can be constructed from an iterator range.

std::list<int> source{
1,
2,
3
};

std::vector<int> destination(
source.begin(),
source.end()
);

Result:

1 2 3

Range construction can copy between different container types when the element types are compatible.


Size Constructor

std::vector<int> values(10);

Creates ten value-initialized ints.

Conceptually:

0 0 0 0 0 0 0 0 0 0

Size and Value Constructor

std::vector<int> values(
5,
10
);

Result:

10 10 10 10 10

array Has Fixed Size

std::array<int, 3> values{
10,
20,
30
};

Its type includes the size.

These are different types:

std::array<int, 3>
std::array<int, 5>

Unlike a built-in array, library array objects can be copied and assigned when their types match.

std::array<int, 3> a{
1,
2,
3
};

std::array<int, 3> b;

b = a;

5) Assignment and swap

Container assignment replaces the target container's elements.

std::vector<int> a{
1,
2,
3
};

std::vector<int> b{
10,
20
};

a = b;

Now:

a = {10, 20}

List Assignment

a = {
100,
200,
300
};

The old elements are replaced.


assign()

assign() replaces all existing elements.

std::vector<int> values;

values.assign(
5,
10
);

Result:

10 10 10 10 10

Assigning a Range

std::list<int> source{
1,
2,
3
};

std::vector<int> destination;

destination.assign(
source.begin(),
source.end()
);

Result:

1 2 3

This allows assignment from a different compatible container type.


swap()

swap() exchanges container contents.

std::vector<int> a{
1,
2
};

std::vector<int> b{
10,
20,
30
};

a.swap(b);

Afterward:

a = {10, 20, 30}
b = {1, 2}

The nonmember form is also available.

swap(a, b);

For most variable-size containers, swapping is efficient because internal container structures are exchanged rather than copying every element.

array differs because its elements must be exchanged.


6) Container Size Operations

Common size operations include:

values.size();
values.empty();
values.max_size();

size()

std::vector<int> values{
10,
20,
30
};

std::cout
<< values.size();

Output:

3

empty()

if (values.empty())
{
std::cout
<< "empty\n";
}

This is normally preferable to:

if (values.size() == 0)

when only emptiness is being checked.


max_size()

auto maximum =
values.max_size();

This reports an upper bound on the number of elements the container can hold.


forward_list

forward_list does not provide a size() member.

Its design avoids storing the additional state required to provide constant-time size information.


7) Relational Operators

Containers of the same type can be compared.

std::vector<int> a{
1,
2,
3
};

std::vector<int> b{
1,
2,
3
};

bool same =
a == b;

Result:

true

Two containers are equal when they contain equal elements in the same order.


Lexicographical Comparison

Container ordering works similarly to dictionary ordering.

std::vector<int> a{
1,
2
};

std::vector<int> b{
1,
3
};

bool result =
a < b;

The comparison examines corresponding elements until a difference is found.

Here:

1 == 1
2 < 3

so:

a < b

is true.

Container comparisons rely on the comparison operations of their element types.


9.3 Sequential Container Operations

Sequential containers provide operations to:

  • add elements
  • access elements
  • remove elements
  • resize containers

1) Adding Elements to a Sequential Container

Important insertion operations include:

OperationMeaning
push_back(t)Add at back
push_front(t)Add at front
insert(p, t)Insert before p
insert(p, n, t)Insert n copies before p
insert(p, b, e)Insert range before p
insert(p, il)Insert initializer list

Not every container supports every operation.


push_back()

std::vector<int> values{
10,
20
};

values.push_back(30);

Result:

10 20 30

push_front()

Containers such as deque and list support insertion at the front.

std::deque<int> values{
20,
30
};

values.push_front(10);

Result:

10 20 30

vector does not provide push_front().


insert()

insert() inserts before the supplied iterator.

std::vector<int> values{
10,
30
};

auto iter =
values.begin() + 1;

values.insert(
iter,
20
);

Result:

10 20 30

Inserting at end()

Passing end() inserts at the back.

values.insert(
values.end(),
40
);

Equivalent in purpose to:

values.push_back(40);

Inserting Multiple Copies

std::vector<int> values{
1,
5
};

values.insert(
values.begin() + 1,
3,
0
);

Result:

1 0 0 0 5

Inserting a Range

std::vector<int> source{
2,
3,
4
};

std::vector<int> destination{
1,
5
};

destination.insert(
destination.begin() + 1,
source.begin(),
source.end()
);

Result:

1 2 3 4 5

Return Value from insert()

insert() returns an iterator to an inserted element.

std::vector<int> values{
10,
30
};

auto iter =
values.insert(
values.begin() + 1,
20
);

std::cout << *iter;

Output:

20

The returned iterator is useful in loops that modify a container.


Emplace Operations

emplace constructs an element directly in the container from constructor arguments.

Suppose:

class Person
{
public:
Person(
std::string name,
int age)
: name(std::move(name)),
age(age)
{
}

private:
std::string name;
int age;
};

Then:

std::vector<Person> people;

people.emplace_back(
"Alice",
20
);

The arguments are used to construct a Person directly at the end of the vector.

Related operations include:

emplace()
emplace_back()
emplace_front()

depending on the container.


2) Accessing Elements

Important access operations include:

OperationMeaning
front()First element
back()Last element
[]Indexed access without checking
at()Indexed access with checking

front()

std::vector<int> values{
10,
20,
30
};

std::cout
<< values.front();

Output:

10

back()

std::cout
<< values.back();

Output:

30

Element Access Returns References

These operations return references.

values.front() = 100;

Now:

100 20 30

Likewise:

values.back() = 300;

operator[]

std::cout
<< values[1];

Indexed access does not perform bounds checking.

An invalid index results in undefined behavior.

// Dangerous:
// values[100]

at()

at() performs bounds checking.

std::cout
<< values.at(1);

For an invalid index:

values.at(100);

the function throws:

std::out_of_range

Checking at()

try
{
std::cout
<< values.at(100);
}
catch (
const std::out_of_range& error)
{
std::cerr
<< error.what()
<< '\n';
}

Empty Container Rule

Do not call:

front()
back()
pop_front()
pop_back()

when the required element does not exist.

Check first:

if (!values.empty())
{
std::cout
<< values.front();
}

3) Erasing Elements

Common removal operations include:

OperationMeaning
pop_back()Remove last element
pop_front()Remove first element
erase(p)Remove element at p
erase(b, e)Remove range
clear()Remove all elements

pop_back()

std::vector<int> values{
10,
20,
30
};

values.pop_back();

Result:

10 20

pop_back() removes the element but does not return it.

If the value is needed:

int value =
values.back();

values.pop_back();

pop_front()

std::deque<int> values{
10,
20,
30
};

values.pop_front();

Result:

20 30

erase()

std::vector<int> values{
10,
20,
30
};

auto iter =
values.begin() + 1;

values.erase(iter);

Result:

10 30

Return from erase()

erase() returns an iterator to the element following the erased element.

std::vector<int> values{
10,
20,
30
};

auto iter =
values.begin();

iter =
values.erase(iter);

Now iter refers to:

20

This return value is important when erasing elements inside a loop.


Erasing a Range

std::vector<int> values{
1,
2,
3,
4,
5
};

values.erase(
values.begin() + 1,
values.begin() + 4
);

The erased range is:

[1, 4)

which contains:

2 3 4

Result:

1 5

clear()

values.clear();

Afterward:

values.empty()

is true.


Erasing While Iterating

A common safe pattern is:

for (
auto iter = values.begin();
iter != values.end();)
{
if (*iter % 2 == 0)
{
iter =
values.erase(iter);
}
else
{
++iter;
}
}

If an element is erased, use the iterator returned by erase().


4) Specialized forward_list Operations

A forward_list stores only a link to the next element.

Therefore insertion and deletion are expressed relative to the element before the position being modified.

Important operations:

before_begin()
insert_after()
emplace_after()
erase_after()

before_begin()

before_begin() represents a special position before the first element.

std::forward_list<int> values{
20,
30
};

auto before =
values.before_begin();

values.insert_after(
before,
10
);

Result:

10 20 30

insert_after()

auto iter =
values.begin();

values.insert_after(
iter,
15
);

If iter refers to 10, the new sequence becomes:

10 15 20 30

erase_after()

auto iter =
values.begin();

values.erase_after(iter);

This removes the element after iter.


Why forward_list Uses after

A singly linked node knows only where the next node is.

Conceptually:

node


next


next

To erase an element efficiently, the container needs access to the preceding node.

That is why the interface uses operations relative to the element before the target.


5) Resizing a Container

resize() changes the number of elements.

std::vector<int> values{
1,
2,
3
};

values.resize(5);

New elements are added at the end.

Result:

1 2 3 0 0

Resize with a Value

values.resize(
7,
10
);

New elements are initialized to 10.


Shrinking

values.resize(2);

Elements after the second are removed.


resize() vs. reserve()

These operations are different.

resize() changes:

number of elements

reserve() changes:

available storage capacity

6) Container Operations May Invalidate Iterators

Modifying a container can invalidate:

  • iterators
  • references
  • pointers

Iterator Invalidation: An iterator no longer refers to a valid position or element after a container modification.


vector Reallocation

Suppose:

std::vector<int> values{
1,
2,
3
};

auto iter =
values.begin();

Then:

values.push_back(4);

may cause the vector to allocate new storage.

If reallocation occurs:

old storage

new larger storage

elements moved

The old iterator is no longer valid.

Do not assume:

// potentially invalid:
// std::cout << *iter;

list Stability

Insertion into list normally does not invalidate iterators or references to existing elements.

std::list<int> values{
1,
3
};

auto iter =
values.begin();

values.insert(
std::next(iter),
2
);

The iterator to the existing first element remains valid.


Erasing Invalidates the Erased Element

Even for containers with stable iterators, an iterator referring to an erased element becomes invalid.

auto iter =
values.begin();

values.erase(iter);

// iter is invalid

Loops That Modify Containers

Use returned iterators from modification operations.

for (
auto iter = values.begin();
iter != values.end();)
{
if (should_remove(*iter))
{
iter =
values.erase(iter);
}
else
{
++iter;
}
}

Do Not Store end() When Modifying the Container

Avoid:

auto end =
values.end();

for (
auto iter = values.begin();
iter != end;
++iter)
{
// operations that modify values
}

A modification may invalidate the saved end.

Prefer evaluating the current end() when necessary.


9.4 How a vector Grows

A vector stores its elements contiguously.

Conceptually:

[10][20][30][40]

This makes random access efficient.

However, the vector needs contiguous space to grow.


Reallocation

Suppose the current allocation is full.

capacity = 4
size = 4

[10][20][30][40]

Adding another element may require new storage.

Conceptually:

allocate larger block

old:
[10][20][30][40]

new:
[10][20][30][40][ ][ ][ ][ ]

The existing elements are transferred to the new storage.

This process is called reallocation.


Why Extra Capacity Is Allocated

If a vector allocated exactly one additional slot for every push_back(), growing repeatedly would require many reallocations.

Instead, vectors usually allocate extra unused capacity.

size = elements currently present
capacity = elements possible before reallocation

size() vs. capacity()

std::vector<int> values;

std::cout
<< values.size()
<< '\n';

std::cout
<< values.capacity()
<< '\n';

size() reports the number of constructed elements.

capacity() reports available element storage before another allocation is needed.

Always:

capacity >= size

reserve()

reserve() requests storage for at least a given number of elements.

std::vector<int> values;

values.reserve(100);

This does not create 100 elements.

values.size();

is still:

0

but the vector may now hold at least 100 elements before requiring another reallocation.


Why Use reserve()

If the approximate number of elements is known:

std::vector<int> values;

values.reserve(1000);

for (int i = 0;
i != 1000;
++i)
{
values.push_back(i);
}

reserving storage may reduce the number of reallocations.


reserve() vs. resize()

std::vector<int> a;

a.reserve(10);

Afterward:

size = 0
capacity >= 10

By contrast:

std::vector<int> b;

b.resize(10);

Afterward:

size = 10
capacity >= 10

resize() creates elements.

reserve() does not.


shrink_to_fit()

values.shrink_to_fit();

requests that unused capacity be reduced.

It is a request to the implementation rather than a guarantee that capacity will change.


9.5 Additional string Operations

string provides operations beyond the common sequential-container interface.

These include:

  • substring construction
  • substr
  • insertion and replacement
  • searching
  • comparison
  • numeric conversion

1) Other Ways to Construct strings

A string can be built from part of another character sequence.


Character Pointer and Count

const char text[] =
"Hello World";

std::string s(
text,
5
);

Result:

Hello

The constructor copies five characters beginning at text.


Substring Constructor

std::string source =
"Hello World";

std::string result(
source,
6
);

Result:

World

The second argument is the starting position.


Position and Length

std::string source =
"Hello World";

std::string result(
source,
6,
3
);

Result:

Wor

substr()

substr() returns a new string containing part of the original.

std::string text =
"Hello World";

std::string result =
text.substr(
6,
5
);

Result:

World

From Position to End

std::string result =
text.substr(6);

Result:

World

2) Other Ways to Change a string

Important modification operations include:

assign()
insert()
erase()
append()
replace()

assign()

Replaces the entire string.

std::string text =
"Hello";

text.assign(
"World"
);

Result:

World

insert()

std::string text =
"HelloWorld";

text.insert(
5,
" "
);

Result:

Hello World

erase()

std::string text =
"Hello World";

text.erase(
5,
1
);

Result:

HelloWorld

append()

std::string text =
"Hello";

text.append(
" World"
);

Result:

Hello World

Similar to:

text += " World";

replace()

std::string text =
"Hello C++";

text.replace(
6,
3,
"World"
);

Result:

Hello World

The selected range is removed and replaced with new characters.


Multiple Overloads

String modification functions support many input forms.

The inserted or replacement characters may come from:

  • another string
  • a character array
  • an iterator range
  • repeated characters

This allows the most convenient interface to be selected for the data available.


3) string Search Operations

The major search operations are:

OperationMeaning
find()First occurrence
rfind()Last occurrence
find_first_of()First character in search set
find_last_of()Last character in search set
find_first_not_of()First character not in set
find_last_not_of()Last character not in set

find()

std::string text =
"hello world";

auto position =
text.find("world");

Result:

6

Match Not Found

When no match exists:

auto position =
text.find("C++");

the result is:

std::string::npos

Therefore:

if (
position
!= std::string::npos)
{
std::cout
<< "found\n";
}

string::npos

string::npos is a special value of type:

std::string::size_type

representing no valid position.

Do not usually store search results in a signed int.

Prefer:

auto position =
text.find("world");

Starting Search Position

Search can begin from a specified position.

std::string text =
"one two one";

auto first =
text.find("one");

auto second =
text.find(
"one",
first + 1
);

Results:

first = 0
second = 8

rfind()

Search from the end.

std::string text =
"one two one";

auto position =
text.rfind("one");

Result:

8

find_first_of()

Find the first occurrence of any character in a search set.

std::string text =
"abc123";

auto position =
text.find_first_of(
"0123456789"
);

Result:

3

The first digit occurs at position 3.


find_first_not_of()

Find the first character not contained in a specified set.

std::string text =
" hello";

auto position =
text.find_first_not_of(
" \t"
);

This can be useful when locating the first non-whitespace character.


4) The compare Functions

compare() compares strings.

std::string a =
"apple";

std::string b =
"banana";

int result =
a.compare(b);

Interpretation:

result < 0
a < b

result == 0
a == b

result > 0
a > b

The exact nonzero value is less important than its sign.


Equal Strings

std::string a =
"hello";

std::string b =
"hello";

if (a.compare(b) == 0)
{
std::cout
<< "equal\n";
}

5) Numeric Conversions

C++ provides functions for converting between strings and numeric values.


to_string()

Converts arithmetic values into strings.

int value = 42;

std::string text =
std::to_string(value);

Result:

"42"

Floating-point example:

double value = 3.14;

std::string text =
std::to_string(value);

stoi()

Converts a string to int.

std::string text =
"123";

int value =
std::stoi(text);

Result:

123

Other Integer Conversions

stoi -> int
stol -> long
stoul -> unsigned long
stoll -> long long
stoull -> unsigned long long

Floating-Point Conversions

stof -> float
stod -> double
stold -> long double

Example:

std::string text =
"3.14159";

double value =
std::stod(text);

Invalid Conversion

std::string text =
"hello";

int value =
std::stoi(text);

cannot produce an integer.

The function throws:

std::invalid_argument

Out-of-Range Conversion

A numeric string may represent a value too large for the target type.

The conversion can then throw:

std::out_of_range

Handling Conversion Errors

try
{
int value =
std::stoi(text);

std::cout << value;
}
catch (
const std::invalid_argument&)
{
std::cerr
<< "not a number\n";
}
catch (
const std::out_of_range&)
{
std::cerr
<< "number too large\n";
}

9.6 Container Adaptors

Adaptor: A mechanism that provides a different interface over an existing type.

The standard container adaptors are:

stack
queue
priority_queue

They use an underlying sequential container to store their elements.

Required headers include:

#include <queue>
#include <stack>

Defining an Adaptor

Basic stack:

std::stack<int> values;

Basic queue:

std::queue<int> values;

Priority queue:

std::priority_queue<int> values;

Default Underlying Containers

The default underlying containers are:

stack
-> deque

queue
-> deque

priority_queue
-> vector

Custom Underlying Container

An adaptor can specify a different compatible container.

Example:

std::stack<
int,
std::vector<int>
> values;

This stack uses a vector<int> internally.


Stack Adaptor

stack: A last-in, first-out container adaptor.

LIFO

Last In
First Out

Conceptually:

push 10

top

[10]

push 20

top

[20]
[10]

push 30

top

[30]
[20]
[10]

stack::push()

std::stack<int> values;

values.push(10);
values.push(20);
values.push(30);

stack::top()

std::cout
<< values.top();

Output:

30

The value is not removed.


stack::pop()

values.pop();

The top element is removed.

Now:

values.top();

returns:

20

pop() does not return the removed value.

If needed:

int value =
values.top();

values.pop();

stack::emplace()

Constructs an object directly at the top.

std::stack<std::string> values;

values.emplace(
5,
'A'
);

The new string is:

AAAAA

Queue Adaptor

queue: A first-in, first-out container adaptor.

FIFO

First In
First Out

Conceptually:

push 10
push 20
push 30

front back
↓ ↓
[10][20][30]

queue::push()

std::queue<int> values;

values.push(10);
values.push(20);
values.push(30);

queue::front()

std::cout
<< values.front();

Output:

10

queue::back()

std::cout
<< values.back();

Output:

30

queue::pop()

values.pop();

Removes the front element.

Now:

front = 20

Priority Queue

priority_queue: A container adaptor that gives access to the highest-priority element.

std::priority_queue<int> values;

values.push(10);
values.push(30);
values.push(20);

The top is:

values.top();

Result:

30

The element inserted first is not necessarily removed first.

Priority determines the order.


Priority Queue Ordering

By default, priority is determined using the element type's ordering.

For integers:

std::priority_queue<int> values;

the largest value is normally at the top.

values.push(5);
values.push(100);
values.push(20);

std::cout
<< values.top();

Output:

100

Removing from a Priority Queue

while (!values.empty())
{
std::cout
<< values.top()
<< ' ';

values.pop();
}

For:

5 100 20

the output is ordered by priority:

100 20 5

Container vs. Container Adaptor

A sequential container exposes its stored sequence directly through operations such as:

begin()
end()
insert()
erase()

An adaptor intentionally exposes a smaller specialized interface.

For example, stack provides:

push()
pop()
top()