Mastering C++ STL: Online Store Modelling and Employee Management Using Containers and Lambdas

Table of contents
- Building an Online Store with C++ STL Containers
- Introduction
- The Data Model
- Leveraging STL Containers
- 1. Vector for Product Catalogue
- 2. Deque for Recent Customer Tracking
- 3. List for Order History
- 4. Set for Unique Categories
- 5. Map for Inventory Management
- 6. Multimap for Customer Orders
- 7. Unordered Containers for Fast Lookups
- Map vs. Unordered Map: When to Use Each
- Struct vs Class: When to Use Each
- Key Differences
- When to Use Structs
- When to Use Classes
- Code Example
- Best Practice Rule of Thumb
- Managing Employees with C++ STL Algorithms and Lambda Expressions

Building an Online Store with C++ STL Containers
Introduction
Today, we'll explore how to implement the foundational elements of an online store using C++ and its powerful Standard Template Library (STL) containers. While real e-commerce platforms typically use web technologies and databases, understanding how to model such systems at a lower level can provide valuable insights into data structure selection and system design.
The Data Model
Our online store has two primary data structures:
struct Product{
int productID;
string name;
string category;
};
struct Order{
int orderID;
int productID;
int quantity;
string customerID;
time_t orderDate;
};
These structures represent the core entities in our system: products available for purchase and orders placed by customers.
Leveraging STL Containers
The power of C++ STL lies in its diverse container types, each optimized for specific operations. Let's see how we've utilized them:
1. Vector for Product Catalogue
vector<Product> products = {
{101,"Laptop","Electronics"},
{102,"SmartPhone","Electronics"},
{103,"Coffe Maker","Kitchen"},
{104,"Blender","Kitchen"},
{105,"Desk Lamp","Home"}
};
Vectors provide dynamic arrays with random access, perfect for our product catalogue where we need indexed access and sequential traversal.
2. Deque for Recent Customer Tracking
deque<string> recentCustomers = {"C001","C002","C003"};
recentCustomers.push_back("C004");
recentCustomers.push_front("C004");
A double-ended queue (deque) allows efficient insertion and deletion at both ends, making it ideal for tracking recent customers in a FIFO (First-In-First-Out) manner, or implementing features like "Recently Viewed."
3. List for Order History
list<Order> orderHistory;
orderHistory.push_back({1,101,2,"C001",time(0)});
orderHistory.push_back({2,102,2,"C002",time(0)});
orderHistory.push_back({3,103,1,"C003",time(0)});
Lists excel at insertion and removal operations anywhere in the sequence, suitable for order history that might require frequent updates.
4. Set for Unique Categories
set<string> categories;
for(const auto &product:products){
categories.insert(product.category);
}
Sets automatically maintain unique elements in a sorted order, perfect for extracting unique product categories without duplicates.
5. Map for Inventory Management
map<int,int> productStock = {
{101,10},
{102,20},
{103,15},
{104,5},
{105,7}
};
Maps provide key-value associations with sorted keys, ideal for tracking product inventory where we need to quickly look up stock levels by product ID.
6. Multimap for Customer Orders
multimap<string,Order> customerOrders;
for(const auto &order:orderHistory){
customerOrders.insert({order.customerID,order});
}
Multimaps allow multiple values for the same key, making them perfect for tracking multiple orders from the same customer.
7. Unordered Containers for Fast Lookups
unordered_map<string,string> customerData = {
{"C001","Alice"},
{"C002","Bob"},
{"C003","Max"},
{"C004","ben"},
{"C005","stokes"}
};
unordered_set<int> uniqueProductIDs;
for(const auto &product : products){
uniqueProductIDs.insert(product.productID);
}
Unordered containers use hash tables for near-constant time lookups, making them excellent for customer data access and product ID validation.
Map vs. Unordered Map: When to Use Each
As noted in the code comments:
//map vs unordered_map
//map stores in sorted order whereas in unordered_map there is no guarantee of sorted data
//map uses binary search tree
//unordered_map uses hash table
This distinction is crucial for performance:
Use
map
when you need elements sorted by key or when you need to find elements within a rangeUse
unordered_map
when you need the fastest possible lookups and don't care about element order
Struct vs Class: When to Use Each
In our online store implementation, we used structs for our data models (Product
and Order
). This wasn't a random choice - it follows C++ best practices. Let's explore when to use each:
Key Differences
The primary technical difference between structs and classes in C++ is the default access level:
In structs, members are public by default
In classes, members are private by default
Beyond this technical distinction, the choice between struct and class communicates design intent:
When to Use Structs
Structs are ideal for:
Simple data containers with little or no behaviour
When all data members can be publicly accessible
Value types representing a single logical entity (like coordinates)
Data Transfer Objects (DTOs) or passive data structures
Our Product
and Order
structs are perfect examples - they primarily store data with minimal behaviour.
When to Use Classes
Classes are better suited for:
Objects that need to maintain invariants (rules that keep data valid)
Data that requires encapsulation (hiding implementation details)
Types with significant behaviour beyond data storage
Objects with clear responsibilities and abstractions
When you need private members and controlled access
Code Example
If we were to expand our online store with more business logic, we might refactor to use classes:
// Using a class for cart management (has behavior + data)
class ShoppingCart {
private:
vector<Order> items;
double totalAmount;
string customerID;
public:
ShoppingCart(string custID) : customerID(custID), totalAmount(0.0) {}
void addItem(Product product, int quantity);
void removeItem(int productID);
double calculateTotal();
void checkout();
};
// Keeping struct for simple data representation
struct PaymentInfo {
string paymentMethod;
string cardNumber;
string expiryDate;
};
Best Practice Rule of Thumb
A common guideline in C++ is:
Use structs when the main purpose is holding related data
Use classes when the type has behaviour that modifies or maintains its data
In our online store example, using structs for Product
and Order
was appropriate as they primarily represent data. If we were to add validation, business rules, or complex behaviour to these entities, a class might become more appropriate.
Managing Employees with C++ STL Algorithms and Lambda Expressions
In this blog, I'll explain how to use C++ Standard Template Library (STL) algorithms and lambda expressions to manage employee data efficiently. This approach demonstrates the power of modern C++ for data manipulation tasks.
Understanding the Employee Struct
struct Employee{
int id;
string name;
double salary;
}
We use a struct (rather than a class) because this is a simple data container with no behavior and all public members. The struct contains an employee's ID, name, and salary - a perfect use case for structs over classes.
The code includes an interesting comment:
//Employee& - reference
//&Employee - address(pointer reference)
This distinguishes between:
Employee&
- a reference to an Employee object&Employee
- the address (pointer) to an Employee
Displaying Employee Information
void displayEmployee(const Employee& emp){
cout<<"ID : "<<emp.id<<" , Name : "<<emp.name<<" Salary : "<<emp.salary<<endl;
}
This function takes a const reference to an Employee (const Employee&
), which prevents modification of the original object while avoiding unnecessary copying.
Vector Initialization
vector<Employee> employees = {
{101,"harsha",1000000},
{102,"ben",1000000},
{103,"salt",1000000},
{104,"rome",1000000},
{105,"phil",1000000}
};
We create a vector of Employee objects using initializer lists - a convenient C++11 feature for creating and populating collections.
Sorting with Lambda Functions
sort(employees.begin(),employees.end(),[](const Employee& e1,const Employee& e2){
return e1.salary>e2.salary;
});
This uses std::sort
with a lambda function to sort employees by salary in descending order. The lambda:
Takes two Employee references as parameters
Returns true if the first employee's salary is greater than the second's
Uses the
[]
capture clause (empty in this case as we don't capture any variables from outside scope)
Displaying with for_each
for_each(employees.begin(),employees.end(),displayEmployee);
The std::for_each
algorithm applies the displayEmployee function to each element in the vector, providing a cleaner alternative to traditional for loops.
Filtering High Earners
vector<Employee> highEarners;
copy_if(employees.begin(),employees.end(),back_inserter(highEarners),[](const Employee& e){
return e.salary>40000;
});
This code:
Creates a new vector for high earners
Uses
std::copy_if
to selectively copy elementsEmploys
back_inserter
to add elements to the destination vectorUses a lambda to define the selection criteria (salary > 40000)
Calculating Total and Average Salary
double totalSalary = accumulate(employees.begin(),employees.end(),0.0,[](double sum,const Employee& e){
return sum+e.salary;
});
double averageSalary = totalSalary/employees.size();
The std::accumulate
algorithm:
Takes a starting value (0.0) and a binary operation (the lambda)
The lambda adds each employee's salary to the running sum
We then calculate the average by dividing by the number of employees
Finding the Highest Paid Employee
auto highestPaid = max_element(employees.begin(),employees.end(),[](const Employee& e1,const Employee& e2){
return e1.salary<e2.salary;
})
std::max_element
finds the largest element according to the comparison function:
The lambda compares salaries
Returns true if the first salary is less than the second
The algorithm returns an iterator to the highest paid employee
Conclusion
This code demonstrates several C++ STL algorithms and lambda expressions working together to manage employee data:
std::sort
for ordering employees by salarystd::for_each
for displaying employee informationstd::copy_if
for filtering based on criteriastd::accumulate
for summing valuesstd::max_element
for finding maximum values
The use of lambda expressions makes the code more readable and concise by allowing us to define operation logic inline where it's used rather than creating separate named functions for each operation.
Subscribe to my newsletter
Read articles from BHEEMISETTY SAI HARSHA directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
