Checkpoint: Data & Memory
Reviewed & published by Brayan K
You've learned vectors, maps/sets, smart pointers, lambdas, auto, range-for, structs and const/constexpr — let's combine them. This checkpoint isn't a new topic; it's the moment where the last several lessons click together into the way real C++ programs are actually written: model data with structs, hold records in a vector, sort and tally them, and manage memory safely.
Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You've Learned
A quick map of the toolkit you're now carrying into the build challenge:
| Tool | What it gives you | Key syntax |
|---|---|---|
| Vectors | growable list of records | vector<T> v; |
| Maps & Sets | lookup by key, tally, dedupe | ++m[key]; |
| Smart Pointers | automatic memory cleanup | make_unique<T>() |
| Lambdas | inline rules for algorithms | [](auto a){...} |
| auto | compiler deduces types | auto x = ...; |
| Range-for | iterate without indices | for (auto& x : c) |
| Structs | bundle related fields | struct T { ... }; |
| const/constexpr | read-only & compile-time | const T& / constexpr |
💡 The Big Picture
Almost every data-handling program follows the same shape you've now learned piece by piece: (1) describe one record with a struct, (2) keep many of them in a std::vector, (3) sort or filter them with a lambda, (4) summarize with a std::map, and (5) manage any heap objects with a smart pointer — all glued together with auto, range-based for, and const-correctness. Carts, leaderboards, inventories, analytics dashboards: under the hood, this is the pattern.
A Quick Tour — Everything Together
Before the challenge, run this short program. It uses every tool from the section in one tidy flow: a struct for a product, a vector of them, a lambda sort, a range-based for with auto, a map tally, and a unique_ptr. If you can read every line comfortably, you're ready.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <algorithm>
using namespace std;
// A quick tour of everything from this section, in one place.
struct Product { // STRUCT: bundle related data
string name;
string category;
double price;
};
int main() {
// VECTOR of structs: a table of records
vector<Product> shop = {
{"Apple", "food", 0.50},
{"Novel", "books", 12.00},
{"Banana", "food", 0.30},
{"Atlas", "books", 25.00},
};
// LAMBDA + sort: order by price, cheapest first
sort(shop.begin(), shop.end(),
[](const Product& a, const Product& b) { return a.price < b.price; });
// RANGE-BASED FOR + auto: iterate without indices
for (const auto& p : shop)
cout << p.name << " (" << p.category << ") $" << p.price << endl;
// MAP: tally how many products per category
map<string, int> perCategory;
for (const auto& p : shop) ++perCategory[p.category];
for (const auto& [cat, n] : perCategory)
cout << cat << ": " << n << endl;
// SMART POINTER: own a heap object, freed automatically
auto featured = make_unique<Product>(shop.front());
cout << "Featured: " << featured->name << endl;
return 0;
}
// ✅ Expected output:
// Banana (food) $0.3
// Apple (food) $0.5
// Novel (books) $12
// Atlas (books) $25
// books: 2
// food: 2
// Featured: Banana🧩 Reorder Challenge
These lines build a vector of structs, sort it with a lambda, and print the first. Put them in order:
A. sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.score > b.score; });
B. struct P { string name; int score; };
C. vector<P> v = {{"Ann", 80}, {"Bo", 95}};
D. cout << v.front().name << endl;
E. int main() {
F. return 0;
G. }Correct order: B → E → C → A → D → F → G
Define the P struct before main (B), open main (E), build the vector (C), sort descending by score with a lambda (A), print the highest scorer's name — Bo (D), then return 0; (F) and close (G).
Build Challenge: Mini Order System
Time to put it all together in one multi-step program. You're given a vector of Order structs. Work through the four steps in the starter — each one exercises a different tool you've learned:
- Sort the orders by total value (quantity * price), highest first, using sort with a lambda comparator.
- Print each order with a range-based for over const auto&.
- Tally total spend per customer in a map<string,double>.
- Capture the single most valuable order in a unique_ptr<Order> and print it.
Try it yourself first, then reveal the full solution to compare.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <algorithm>
using namespace std;
// 🎯 BUILD CHALLENGE: Mini order system
// Combine struct + vector + lambda + map + smart pointer.
struct Order {
string customer;
string item;
int quantity;
double price; // price per unit
};
int main() {
vector<Order> orders = {
{"Mia", "Pen", 3, 1.50},
{"Leo", "Notebook", 2, 4.00},
{"Mia", "Eraser", 5, 0.40},
{"Zoe", "Notebook", 1, 4.00},
};
// STEP 1: Sort orders by total value (quantity * price), highest first.
// Use sort() with a lambda comparator.
// TODO
// STEP 2: Print each order as "customer: item xqty = total".
// Use a range-based for with const auto&.
// TODO
// STEP 3: Tally total spend PER CUSTOMER in a map<string,double>.
// Then print each customer and their total.
// TODO
// STEP 4: Find the single most valuable order and store a COPY of it
// in a unique_ptr<Order>, then print its customer & total.
// TODO
return 0;
}One worked solution. It even adds a const total() member to the struct to keep the comparator and printing tidy. Run it and read the expected output in the comment at the bottom.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <algorithm>
using namespace std;
struct Order {
string customer;
string item;
int quantity;
double price;
double total() const { return quantity * price; } // const method
};
int main() {
vector<Order> orders = {
{"Mia", "Pen", 3, 1.50},
{"Leo", "Notebook", 2, 4.00},
{"Mia", "Eraser", 5, 0.40},
{"Zoe", "Notebook", 1, 4.00},
};
// STEP 1: sort by total value, highest first
sort(orders.begin(), orders.end(),
[](const Order& a, const Order& b) { return a.total() > b.total(); });
// STEP 2: print each order
cout << "--- Orders (by value) ---" << endl;
for (const auto& o : orders)
cout << o.customer << ": " << o.item << " x" << o.quantity
<< " = " << o.total() << endl;
// STEP 3: tally spend per customer
map<string, double> spend;
for (const auto& o : orders) spend[o.customer] += o.total();
cout << "--- Spend per customer ---" << endl;
for (const auto& [name, amount] : spend)
cout << name << ": " << amount << endl;
// STEP 4: top order in a smart pointer (orders[0] after sorting)
auto top = make_unique<Order>(orders.front());
cout << "--- Top order ---" << endl;
cout << top->customer << " spent " << top->total()
<< " on " << top->item << endl;
return 0;
}
/* Order totals: Pen 4.5, Notebook(Leo) 8, Eraser 2, Notebook(Zoe) 4
Expected output:
--- Orders (by value) ---
Leo: Notebook x2 = 8
Mia: Pen x3 = 4.5
Zoe: Notebook x1 = 4
Mia: Eraser x5 = 2
--- Spend per customer ---
Leo: 8
Mia: 6.5
Zoe: 4
--- Top order ---
Leo spent 8 on Notebook
*/
// ✅ Expected output:
// --- Orders (by value) ---
// Leo: Notebook x2 = 8
// Mia: Pen x3 = 4.5
// Zoe: Notebook x1 = 4
// Mia: Eraser x5 = 2
// --- Spend per customer ---
// Leo: 8
// Mia: 6.5
// Zoe: 4
// --- Top order ---
// Leo spent 8 on Notebook📝 Checkpoint Quiz
Answer each in your head, then reveal it. These cover the whole section.
1. You have a vector<Student> and want them sorted by score descending. What do you pass to std::sort?
A lambda comparator that returns true when the first should come first: [](const Student& a, const Student& b){ return a.score > b.score; }. Returning > gives descending order.
2. Which container counts how many items belong to each category, and what's the one-line tally idiom?
A map<string,int> (or unordered_map). The idiom is ++counts[category]; — a missing key is value-initialized to 0 first.
3. Why use make_unique<Order>(...) instead of new Order(...)?
The unique_ptr frees the object automatically when it goes out of scope — no manual delete, no leaks or double-frees. make_unique is also exception-safe and avoids writing raw new.
4. In for (const auto& o : orders), why const auto& rather than plain auto?
const auto& reads each Order without copying it (the &) and promises not to modify it (the const). Plain auto would copy every struct on each iteration.
5. What's the difference between const and constexpr for a value like a maximum size?
const means it can't be reassigned (may be set at run time); constexpr guarantees it's known at compile time, so it can size a fixed array. Use constexpr when you need a true compile-time constant.
6. After sort(...) by value descending, where is the most valuable order, and how do you copy it into a smart pointer?
It's at the front: orders.front() (i.e. orders[0]). Copy it with auto top = make_unique<Order>(orders.front());, then access fields via top->.
🎉 Checkpoint Complete
- ✅ You modeled records with struct and stored many in a std::vector
- ✅ You sorted records with a lambda comparator and iterated with range-based for
- ✅ You tallied data with a std::map using the ++m[key] idiom
- ✅ You managed a heap object safely with make_unique — no manual cleanup
- ✅ You wrote it all const-correctly with auto for clean, modern C++
Practice quiz
Which std::vector member function adds an element to the end of the vector?
- push_front
- append
- push_back
- insert_back
Answer: push_back. std::vector grows from the end with push_back; there is no push_front.
To sort a vector of structs by a field in descending order, what do you pass as the third argument to std::sort?
- A lambda comparator returning a.field > b.field
- The field name as a string
- The vector's .size()
- Nothing — sort always descends
Answer: A lambda comparator returning a.field > b.field. std::sort takes a comparator; a lambda returning a.field > b.field gives descending order.
In a range-based for loop, why prefer 'const auto& x' over plain 'auto x' when only reading elements?
- It runs the loop in parallel
- It is required for std::map
- auto is not allowed in range-for
- It avoids copying each element and prevents accidental modification
Answer: It avoids copying each element and prevents accidental modification. const auto& binds a reference so no copy is made, and const forbids modifying the element.
What does the expression ++counts[category] do when 'category' is not yet a key in a std::map<string,int>?
- Throws an out_of_range exception
- Inserts the key with value 0, then increments it to 1
- Does nothing
- Increments a random existing entry
Answer: Inserts the key with value 0, then increments it to 1. operator[] value-initializes a missing key to 0, then ++ makes it 1 — the classic tally idiom.
Which header must be included to use std::sort?
- <algorithm>
- <vector>
- <map>
- <memory>
Answer: <algorithm>. std::sort lives in <algorithm>, separate from the container headers.
What is the main advantage of std::make_unique<T>(...) over 'new T(...)'?
- It is faster at runtime
- It allows the object to be shared by many owners
- It frees the object automatically when the unique_ptr goes out of scope
- It allocates on the stack instead of the heap
Answer: It frees the object automatically when the unique_ptr goes out of scope. A unique_ptr ties the heap object's lifetime to scope, freeing it automatically with no manual delete.
How do you access a member of an object owned by a std::unique_ptr<Order> named 'top'?
- top.member
- top->member
- top::member
- *top.member
Answer: top->member. A smart pointer is dereferenced with ->, just like a raw pointer, e.g. top->customer.
What does a struct give you in C++?
- Automatic memory cleanup
- A growable list of elements
- A compile-time constant
- A way to bundle related fields into one record type
Answer: A way to bundle related fields into one record type. A struct groups related data fields together into a single named record type.
What is the difference between 'const' and 'constexpr' for a value?
- They are identical
- const means it cannot be reassigned; constexpr also guarantees the value is known at compile time
- constexpr values can be reassigned but const cannot
- const works only with integers
Answer: const means it cannot be reassigned; constexpr also guarantees the value is known at compile time. constexpr is a stronger guarantee — a true compile-time constant usable to size fixed arrays.
After sorting a vector descending by value, where is the most valuable element?
- At v.back()
- At the middle index
- At v.front() (index 0)
- It must be searched for with std::find
Answer: At v.front() (index 0). A descending sort puts the largest element first, so it is at v.front(), i.e. v[0].
Continue this course
- Previous: const & constexpr
- Next: Templates — Write generic functions and classes that work with any type
- Quick reference: C++ cheat sheet
Frequently asked questions
What should I be able to do after this checkpoint?
You should be able to model data with structs, store records in a vector, sort them with a lambda, tally values with a map, and manage a heap object with a smart pointer — all using auto, range-based for loops, and const-correctness naturally.
How do I combine a vector, a struct, and a lambda?
Define a struct for one record, store many in a std::vector, then call std::sort with a lambda comparator that reads the struct's fields. This 'table of records that you sort and filter' is one of the most common patterns in real C++ programs.
When do I reach for a map versus a vector here?
Use a vector when you have an ordered list of records to iterate or sort. Use a map when you need to look something up by a key or tally counts per category, like counting how many items fall into each group.
Why use a smart pointer in the build challenge?
When an object lives on the heap and must be cleaned up exactly once, a std::unique_ptr ties its lifetime to scope so it is freed automatically. It removes manual delete calls and the leaks and double-frees that come with them.