Vector — Dynamic Array, Capacity, emplace_back, shrink_to_fit
In this tutorial, you will learn about Vector. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ std::vector is a dynamically-resizable array with contiguous storage, providing amortized constant-time push_back, random access, and fine-grained control over capacity and memory allocation.
What You'll Learn
You will create and manipulate vectors, understand capacity and size management including reserve, resize, and shrink_to_fit, use emplace_back and emplace for efficient in-place construction, manage Iterator invalidation during insertion and erasure, and implement the erase-remove idiom for element removal.
Why It Matters
std::vector is the most widely used standard library container. Its contiguous memory layout matches the processor's cache hierarchy, making it faster than linked lists in most scenarios. Understanding vector's growth Strategy, capacity management, and emplacement is essential for writing efficient C++ code.
Learning Path
graph LR
A["29: STL Overview"] --> B["30: Vector"]
B --> C["31: Deque, List, Forward List"]
C --> D["32: Set & Multiset"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Creating and Initializing
#include <iostream>
#include <vector>
int main() {
// Default constructor: empty vector
std::vector<int> v1;
// Size + value constructor
std::vector<int> v2(5, 10); // {10, 10, 10, 10, 10}
// Initializer list (C++11)
std::vector<int> v3 = {1, 2, 3, 4, 5};
// Copy constructor
std::vector<int> v4 = v3;
// Range constructor (from iterators)
std::vector<int> v5(v3.begin() + 1, v3.end() - 1); // {2, 3, 4}
for (int x : v5) std::cout << x << " ";
std::cout << "\n";
}
Accessing Elements
#include <iostream>
#include <vector>
#include <stdexcept>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// operator[] — unchecked, fast
std::cout << vec[2] << "\n"; // 30
// at() — bounds-checked (throws std::out_of_range)
try {
std::cout << vec.at(10) << "\n";
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << "\n";
}
// front() and back()
std::cout << vec.front() << " " << vec.back() << "\n"; // 10 50
// data() — raw pointer to underlying array
int* raw = vec.data();
raw[0] = 99;
std::cout << vec[0] << "\n"; // 99
}
Size vs Capacity
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << "\n";
vec.push_back(1);
std::cout << "After push_back: size=" << vec.size()
<< ", capacity=" << vec.capacity() << "\n";
// Reserve space to avoid reallocations
vec.reserve(100);
std::cout << "After reserve(100): size=" << vec.size()
<< ", capacity=" << vec.capacity() << "\n";
// Shrink to fit (may reallocate)
vec.shrink_to_fit();
std::cout << "After shrink_to_fit: size=" << vec.size()
<< ", capacity=" << vec.capacity() << "\n";
// resize: changes size, default-inserting or removing elements
vec.resize(5);
std::cout << "After resize(5): size=" << vec.size() << "\n";
for (int x : vec) std::cout << x << " ";
std::cout << "\n";
}
Growth Strategy
Different implementations use different growth factors:
- GCC (libstdc++): 2x
- Clang (libc++): 2x
- MSVC: 1.5x
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
for (int i = 0; i < 20; ++i) {
vec.push_back(i);
std::cout << "size=" << vec.size() << ", capacity=" << vec.capacity() << "\n";
}
}
Expected output (with 2x growth factor):
size=1, capacity=1
size=2, capacity=2
size=3, capacity=4
size=4, capacity=4
size=5, capacity=8
...
emplace_back and emplace
#include <iostream>
#include <vector>
#include <string>
struct Point {
int x, y;
Point(int a, int b) : x(a), y(b) {
std::cout << "Point(" << x << "," << y << ")\n";
}
};
int main() {
std::vector<Point> points;
// push_back: creates temporary, then moves/copies
points.push_back(Point(1, 2));
// emplace_back: constructs in place (more efficient)
points.emplace_back(3, 4); // passes arguments directly to constructor
// emplace at position
points.emplace(points.begin() + 1, 5, 6);
// With strings
std::vector<std::string> strings;
strings.emplace_back(10, 'a'); // constructs "aaaaaaaaaa" in place
strings.push_back("hello");
for (const auto& s : strings) {
std::cout << s << "\n";
}
}
emplace_back forwards its arguments directly to the constructor, avoiding the temporary object created by push_back.
Inserting and Erasing
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// Insert at position
vec.insert(vec.begin() + 2, 99); // {1, 2, 99, 3, 4, 5}
// Insert multiple copies
vec.insert(vec.end(), 3, 100); // {..., 100, 100, 100}
// Insert from another range
std::vector<int> more = {200, 201};
vec.insert(vec.end(), more.begin(), more.end());
// Erase single element
vec.erase(vec.begin() + 1); // removes element at index 1
// Erase range
vec.erase(vec.begin() + 2, vec.begin() + 4);
// Clear all
vec.clear();
}
The Erase-Remove Idiom
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
// Remove all occurrences of 2
auto newEnd = std::remove(vec.begin(), vec.end(), 2);
// remove shifts non-removed elements to the front, returns new logical end
vec.erase(newEnd, vec.end()); // actually shrink the vector
for (int x : vec) std::cout << x << " ";
std::cout << "\n"; // 1 3 4 5
// Alternative: single line
// vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
// Conditional remove
vec.erase(std::remove_if(vec.begin(), vec.end(),
[](int x) { return x % 2 == 0; }),
vec.end());
for (int x : vec) std::cout << x << " ";
std::cout << "\n"; // 1 3 5
}
Common Mistakes
Mistake 1: Not Reserving
std::vector<int> v;
for (int i = 0; i < 100000; ++i) v.push_back(i); // 17 reallocations
Always reserve if you know the size in advance.
Mistake 2: Using resize Instead of reserve
v.resize(1000); // creates 1000 default-constructed elements
Use reserve to allocate memory without creating elements.
Mistake 3: Storing bool in vector
std::vector<bool> is a bitset specialization, not a container of bools. It does not meet container requirements. Use std::deque<bool> or std::vector<char> for real bools.
Mistake 4: Iterator Invalidation After Insertion/Erase
After inserting or erasing, all iterators at or after the modification point are invalidated. Use the returned iterator.
Mistake 5: Passing Vector by Value
void process(std::vector<int> v) { ... } // copies entire vector
Pass by const& for read-only, or by && for move semantics.
Mistake 6: Ignoring the Small Vector Optimization
std::vector always heap-allocates. For small, fixed-size arrays, prefer std::array or std::string for short strings (small string optimization).
Practice Questions
- What is the difference between
size()andcapacity()? - When should you use
emplace_backinstead ofpush_back? - What is the erase-remove idiom and why is it needed?
- What happens to iterators after
push_backcauses a reallocation? - Why is
std::vector<bool>special and often problematic?
Challenge
Implement a function compact that takes a vector of optional values (std::vector<std::optional<int>>) and returns a vector containing only the present values, preserving order.
FAQ
Mini Project
Build a growable matrix using vector of vector:
#include <iostream>
#include <vector>
class Matrix {
private:
std::vector<std::vector<double>> data_;
public:
Matrix(size_t rows, size_t cols)
: data_(rows, std::vector<double>(cols, 0.0)) {}
double& operator()(size_t r, size_t c) {
return data_[r][c];
}
const double& operator()(size_t r, size_t c) const {
return data_[r][c];
}
size_t rows() const { return data_.size(); }
size_t cols() const { return data_.empty() ? 0 : data_[0].size(); }
Matrix operator*(const Matrix& other) const {
Matrix result(rows(), other.cols());
for (size_t i = 0; i < rows(); ++i) {
for (size_t k = 0; k < cols(); ++k) {
for (size_t j = 0; j < other.cols(); ++j) {
result(i, j) += data_[i][k] * other(k, j);
}
}
}
return result;
}
void print() const {
for (size_t i = 0; i < rows(); ++i) {
for (size_t j = 0; j < cols(); ++j) {
std::cout << data_[i][j] << "\t";
}
std::cout << "\n";
}
}
};
int main() {
Matrix a(2, 3);
a(0, 0) = 1; a(0, 1) = 2; a(0, 2) = 3;
a(1, 0) = 4; a(1, 1) = 5; a(1, 2) = 6;
Matrix b(3, 2);
b(0, 0) = 7; b(0, 1) = 8;
b(1, 0) = 9; b(1, 1) = 10;
b(2, 0) = 11; b(2, 1) = 12;
Matrix c = a * b;
c.print();
}
What's Next
Vector is your default container. The next lesson covers deque, list, and forward_list: their performance characteristics and when they outperform vector.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro