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:

ToolWhat it gives youKey syntax
Vectorsgrowable list of recordsvector<T> v;
Maps & Setslookup by key, tally, dedupe++m[key];
Smart Pointersautomatic memory cleanupmake_unique<T>()
Lambdasinline rules for algorithms[](auto a){...}
autocompiler deduces typesauto x = ...;
Range-foriterate without indicesfor (auto& x : c)
Structsbundle related fieldsstruct T { ... };
const/constexprread-only & compile-timeconst 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:

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

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

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.

Related lessons