Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Using std::vector as a Stack
Use push_back and pop_back for LIFO stack operations.
std::vector and stack behavior
When collecting user input of unknown quantity, pre-sizing the vector is impractical. Stack operations provide an elegant solution.
The input collection problem
Suppose you're building a gradebook application where teachers enter student scores. You don't know in advance how many scores they'll enter. Several approaches exist, but all have drawbacks:
- Pre-ask for count: Forces users to count entries manually
- Guess maximum size: Wasteful and artificially limiting
- Manual resize logic: Mixes array management with program logic
There's a better way: stack operations.
Understanding stacks
Imagine a dispenser holding cafeteria trays. Due to the heavy trays, you can only interact with the top tray:
- Place a new tray on top (hiding the one below)
- Remove the top tray (exposing the one below)
You cannot access middle or bottom trays without first removing trays above them.
This describes last-in, first-out (LIFO) ordering: the last tray added is the first removed.
Stacks in programming
A stack is a container following LIFO ordering, implementing two core operations:
| Operation | Behavior | Required |
|---|---|---|
| Push | Add element to top | Yes |
| Pop | Remove top element | Yes |
Common optional operations:
| Operation | Behavior | Required |
|---|---|---|
| Top/Peek | View top element | No |
| Empty | Check if stack is empty | No |
| Size | Count elements | No |
C++ adds stack capabilities to existing containers rather than creating separate stack types. This provides both stack operations and the container's native functionality.
Stack operations on std::vector
std::vector implements stack behavior through these methods:
| Function | Stack Operation | Behavior |
|---|---|---|
push_back() |
Push | Add element to end |
emplace_back() |
Push | Efficiently construct element at end |
pop_back() |
Pop | Remove element from end |
back() |
Top/Peek | View last element |
Example:
#include <iostream>
#include <vector>
void display(const std::vector<int>& stack)
{
if (stack.empty())
std::cout << "Empty";
for (auto val : stack)
std::cout << val << ' ';
std::cout << " Capacity: " << stack.capacity()
<< " Length: " << stack.size() << '\n';
}
int main()
{
std::vector<int> stack {};
display(stack);
stack.push_back(5);
display(stack);
stack.push_back(10);
display(stack);
stack.push_back(15);
display(stack);
std::cout << "Top: " << stack.back() << '\n';
stack.pop_back();
display(stack);
stack.pop_back();
display(stack);
return 0;
}
Sample output:
Empty Capacity: 0 Length: 0
5 Capacity: 1 Length: 1
5 10 Capacity: 2 Length: 2
5 10 15 Capacity: 4 Length: 3
Top: 15
5 10 Capacity: 4 Length: 2
5 Capacity: 4 Length: 1
Notice push_back() increments length and reallocates when necessary.
`push_back()` and `emplace_back()` increment vector length and reallocate if capacity is insufficient.
Reallocation with extra capacity
When pushing triggers reallocation, vectors often allocate extra capacity:
In the example above, the third push increased capacity from 2 to 4 (not just 3). This strategy reduces future reallocations:
- GCC/Clang: Doubles capacity
- Visual Studio: Multiplies capacity by 1.5
This explains varying output across compilers.
Why resize() doesn't work for stacks
You might try pre-allocating capacity with resize:
#include <iostream>
#include <vector>
void display(const std::vector<int>& stack)
{
for (auto val : stack)
std::cout << val << ' ';
std::cout << " Capacity: " << stack.capacity()
<< " Length: " << stack.size() << '\n';
}
int main()
{
std::vector<int> stack(3); // Resize to 3
display(stack);
stack.push_back(5);
display(stack);
stack.push_back(10);
display(stack);
return 0;
}
Output:
0 0 0 Capacity: 3 Length: 3
0 0 0 5 Capacity: 6 Length: 4
0 0 0 5 10 Capacity: 6 Length: 5
The problem: resize() sets both capacity AND length, creating three zero-initialized elements. Pushing adds elements after these zeros.
Using reserve() for stack operations
reserve() increases capacity without changing length:
#include <iostream>
#include <vector>
void display(const std::vector<int>& stack)
{
if (stack.empty())
std::cout << "Empty";
for (auto val : stack)
std::cout << val << ' ';
std::cout << " Capacity: " << stack.capacity()
<< " Length: " << stack.size() << '\n';
}
int main()
{
std::vector<int> stack {};
display(stack);
stack.reserve(10); // Reserve capacity without changing length
display(stack);
stack.push_back(5);
display(stack);
stack.push_back(10);
display(stack);
return 0;
}
Output:
Empty Capacity: 0 Length: 0
Empty Capacity: 10 Length: 0
5 Capacity: 10 Length: 1
5 10 Capacity: 10 Length: 2
Perfect! The capacity increased without creating unwanted elements.
`resize()` changes length (and capacity if needed). `reserve()` changes only capacity.
When increasing vector size: - Use `resize()` when indexing elements (changes length) - Use `reserve()` when using stack operations (doesn't change length)
push_back() vs emplace_back()
When the object already exists, use push_back():
int value { 42 };
stack.push_back(value); // Preferred
When creating a temporary to push, emplace_back() can be more efficient:
stack.push_back({ "Hello", 5 }); // Creates temporary, then copies
stack.emplace_back("Hello", 5); // Constructs directly in vector (no copy)
emplace_back() forwards arguments to construct the object in-place, avoiding copies for expensive types.
Prefer `emplace_back()` when creating temporaries or accessing explicit constructors. Prefer `push_back()` otherwise.
Solving the input collection problem
Stack operations elegantly handle unknown input quantities:
#include <iostream>
#include <limits>
#include <vector>
int main()
{
std::vector<int> grades {};
std::cout << "Enter grades (-1 to finish): ";
while (true)
{
int grade {};
std::cin >> grade;
if (!std::cin)
{
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
if (grade == -1)
break;
grades.push_back(grade);
}
std::cout << "You entered " << grades.size() << " grades: ";
for (const auto& g : grades)
std::cout << g << ' ';
std::cout << '\n';
return 0;
}
No counting, no pre-sizing, no manual memory management—just pure logic!
Summary
Stack behavior: A stack is a last-in, first-out (LIFO) container that supports push (add to top) and pop (remove from top) operations. C++ adds stack functionality to existing containers like std::vector rather than creating separate stack types.
Vector stack operations: push_back() and emplace_back() add elements to the end, pop_back() removes the last element, and back() views the last element without removing it. These operations automatically manage vector length and capacity.
The input collection problem: When collecting unknown quantities of input, stack operations provide an elegant solution. Users can enter items one by one, with each entry automatically added via push_back() without pre-determining array size.
resize() vs reserve(): resize() changes both length and capacity, creating elements that occupy indices. reserve() only changes capacity without creating elements, making it appropriate when using stack operations to build up a vector.
push_back() vs emplace_back(): Use push_back() when pushing existing objects. Use emplace_back() when constructing objects in-place from arguments, which avoids unnecessary copies for expensive types.
Reallocation strategies: When push_back() triggers reallocation, implementations typically allocate extra capacity (GCC/Clang double capacity, Visual Studio multiplies by 1.5) to reduce future reallocation frequency.
Stack operations transform std::vector from a simple array into a powerful, self-managing data structure perfect for scenarios where the final size isn't known upfront.
Using std::vector as a Stack - Quiz
Test your understanding of the lesson.
Practice Exercises
std::vector Stack Behavior
Practice using std::vector as a stack with push_back and pop_back. Learn to dynamically add and remove elements.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!