C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
63: Vectors: Growth and Performance
I once worked with a junior dev on a real-time physics engine for a small indie project. He had a system that spawned debris particles whenever an object shattered. Everything looked buttery smooth for the first few seconds, but then he noticed these rhythmic, microscopic stutters—"micro-hitches," we call them. He checked his algorithms; they were $O(n)$. He checked his memory leaks; there were none. It took me about ten minutes of profiling to show him that the hitches happened exactly when his std::vector<Particle> hit its capacity limit and had to reallocate.
If you've been using vectors, you know they're great because they feel like dynamic arrays. You just keep calling push_back() and the vector "grows." But in C++, nothing grows for free. A vector is just a thin wrapper around a raw heap-allocated array. When you add an element and the underlying array is full, the vector can't just "expand" the existing memory—it might be blocked by other data on the heap. Instead, it has to find a completely new, larger chunk of memory, copy every single existing element over to the new location, and then delete the old array.
The Mechanics of Exponential Growth
To keep push_back() efficient, C++ vectors don't just grow by one slot every time they fill up; that would be a performance nightmare. Instead, they use a growth factor—usually doubling the capacity (though some implementations use 1.5x). This is why we say push_back() has an amortized constant time complexity. Most of the time, it's just a pointer increment and an assignment. Every once in a while, it's a massive move operation.
You can see this in action by tracking the difference between size() and capacity(). The size is how many elements are actually in the vector; the capacity is how many it could hold before it needs to reallocate. Watch what happens here:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
for(int i = 0; i < 10; ++i) {
vec.push_back(i);
std::cout < "Size: " < vec.size() < " Capacity: " < vec.capacity() < "\n";
}
return 0;
}
If you run this, you'll see the capacity jump in powers of two (or similar). It’s a clever trade-off: we waste a little bit of memory to avoid spending too much time copying data.
Eliminating Reallocations with Reserve
The "hitch" my colleague experienced happened because he was pushing thousands of particles into a vector without telling the vector how many to expect. The vector was reallocating and copying its entire contents dozens of times per frame. The fix is simple: reserve().
When you call vec.reserve(1000), you're telling the vector, "I don't have the data yet, but I know I'm going to need room for at least 1,000 elements." The vector allocates that memory immediately. Now, the first 1,000 calls to push_back() are guaranteed to be fast, with zero reallocations. I always tell people: if you have a rough idea of how much data you're about to load, reserve() is the single easiest performance win in the C++ standard library.
Just be careful not to confuse reserve() with resize(). resize() actually changes the size of the vector and default-constructs new elements. reserve() only changes the capacity; the vector still thinks it's empty, it just has a bigger "parking lot" ready for the data you're about to bring in.
📋 Practical Task
Performance Benchmarking: The Particle Burst Simulator
In this exercise, you will build a small simulation to quantify the performance difference between a "naive" vector growth strategy and an optimized "reserved" strategy.
Your Task:
- Create a program that simulates loading 100,000 "Particle" objects (a simple struct with three doubles for x, y, z coordinates) into a
std::vector. - Implement two separate functions:
loadNaive()which simply callspush_back()in a loop, andloadOptimized()which callsreserve(100000)before the loop. - Use the
<chrono>library to measure the execution time of both functions. - Print the time difference to the console.
Success Criteria: Your program should clearly demonstrate that loadOptimized() is faster than loadNaive(), and you should see a significant reduction in the time spent populating the vector.
There are no comments for now.