C++ Programming – Unit IV & V | Complete Student Notes B.E./B.
Tech CSE
C++ PROGRAMMING
Complete Student Notes – Expanded Edition
Unit IV: Standard Template Libraries | Unit V: Lambda & Concurrency
Subject Object Oriented Programming with C++
Units Unit IV (STL & File Handling) + Unit V (Lambda & Concurrency)
Level B.E./[Link] – Computer Science & Engineering
Edition Expanded – with in-depth explanations, analogies & examples
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 1
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
TABLE OF CONTENTS
UNIT IV – STANDARD TEMPLATE LIBRARIES
• 1. Introduction to STL – Components, Philosophy & Design
• 2. Sequence Containers – array, vector, deque, list, forward_list
• 3. Container Adaptors – stack, queue, priority_queue
• 4. Associative Containers – set, multiset, map, multimap
• 5. Unordered (Hash) Containers – all four variants
• 6. STL Algorithms – sorting, searching, modifying
• 7. Iterators – types, categories, adapters
• 8. Functors & Function Objects
• 9. File Handling – text, binary, random access
UNIT V – LAMBDA & CONCURRENCY
• 10. Exception Handling – types, hierarchy, custom exceptions
• 11. Concurrency – thread, mutex, atomic, condition_variable
• 12. Lambda Expressions – syntax, captures, generics
• 13. Smart Pointers – unique_ptr, shared_ptr, weak_ptr
• 14. lvalue, rvalue & Perfect Forwarding
• 15. Templates – function, class, variadic, SFINAE, Concepts
• 16. Quick Revision & Exam Tips
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 2
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
UNIT IV
Standard Template Libraries (STL) & File Handling
1. Introduction to the Standard Template Library (STL)
The Standard Template Library (STL) is a collection of ready-to-use, generic (template-based) data structures
and algorithms built into the C++ Standard Library. It was originally designed by Alexander Stepanov and
Meng Lee at Hewlett-Packard, later adopted into the ISO C++ standard (1994). Rather than re-implementing
linked lists, sorting algorithms, or hash tables from scratch, a programmer can use the battle-tested, highly
optimised implementations provided by STL.
■ Think of STL as a well-stocked kitchen. The containers are the pots and bowls (they hold food/data). The
algorithms are the cooking techniques (boil, sort, chop). The iterators are the spoons that let you reach inside any
container in the same way.
1.1 Why STL?
■ Reusability – Generic code works with any data type via templates.
■ Efficiency – STL implementations are heavily optimised; many containers/algorithms achieve theoretical
best complexity.
■ Correctness – Widely tested standard implementations reduce bugs.
■ Interoperability – All STL containers work seamlessly with STL algorithms through iterators.
■ Productivity – Massive reduction in boilerplate code.
1.2 Four Pillars of STL
Pillar Role Examples
Containers Store and organise data vector, list, map, set, queue
Algorithms Process data in containers sort, find, count, transform, copy
Iterators Bridge between containers & algorithms begin(), end(), ++it, *it
Functors Callable objects used in algorithms greater<>, less<>, plus<>, custom
■ Note: Algorithms never depend on the internal structure of containers — they only use iterators. This separation
is one of STL's most elegant design decisions.
1.3 Header Files Quick Reference
Header Contents
<vector> std::vector
<list> std::list (doubly linked)
<forward_list> std::forward_list (singly linked)
<deque> std::deque
<array> std::array (fixed-size)
<stack>, <queue> stack, queue, priority_queue
<set> set, multiset
<map> map, multimap
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 3
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
Header Contents
<unordered_set> unordered_set, unordered_multiset
<unordered_map> unordered_map, unordered_multimap
<algorithm> sort, find, copy, transform, etc.
<iterator> Iterator adapters & utilities
<functional> Functors: greater<>, less<>, bind, function
<numeric> accumulate, inner_product, iota
2. Sequence Containers
Sequence containers store elements in a linear order. The position of each element is determined by the order
in which it was inserted (unless explicitly changed). The programmer has full control over the order of elements.
2.1 std::array (C++11 – Fixed-Size Array)
What it is: A thin wrapper around a plain C-style array. The size is a compile-time constant — you cannot add
or remove elements. Unlike raw C arrays, std::array knows its own size, provides iterators, and works with STL
algorithms.
Memory layout: Elements are stored contiguously on the stack (not heap), making it extremely cache-friendly
and fast.
■ Analogy: A fixed-size egg carton. You can change which egg is in each slot, but the carton always holds
exactly 12 eggs — no more, no less.
#include <array>
#include <algorithm>
std::array<int, 5> arr = {30, 10, 50, 20, 40};
// Element access
arr[2]; // 50 – unchecked, UB if out of range
[Link](2); // 50 – checked, throws std::out_of_range
[Link](); // 30 – first element
[Link](); // 40 – last element
[Link](); // pointer to raw array
// Size & state
[Link](); // 5
arr.max_size(); // 5 (same as size for array)
[Link](); // false
// Utility
[Link](0); // set all elements to 0
std::sort([Link](), [Link]()); // works with STL algorithms
// Range-based for loop
for(int x : arr) std::cout << x << ' ';
When to use std::array:
■ Size is known at compile time and will never change
■ You want stack allocation for performance
■ You need STL algorithm compatibility with C-style arrays
■ Examples: fixed-size buffers, lookup tables, matrix rows
2.2 std::vector (Dynamic Array)
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 4
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
What it is: The most commonly used STL container. A dynamic array that automatically resizes when elements
are added. Internally, vector allocates a contiguous block of heap memory and doubles its capacity when more
space is needed (amortised O(1) push_back).
Memory layout: Elements are contiguous in memory, enabling O(1) random access via index and excellent
CPU cache performance.
■ Analogy: A magic backpack that doubles in size every time it fills up. You can instantly reach any pocket by
number (random access), and adding to the back is cheap. But inserting something in the middle means shifting
everything.
#include <vector>
std::vector<int> v; // empty vector
std::vector<int> v2(5, 0); // 5 elements, all 0
std::vector<int> v3 = {1, 2, 3, 4, 5};
// Adding / removing elements
v3.push_back(6); // add to end O(1) amortised
v3.pop_back(); // remove last O(1)
[Link]([Link]()+2, 99);// insert at index 2 O(n)
[Link]([Link]()); // erase first O(n)
v3.emplace_back(7); // construct in-place (faster than push_back)
// Access
v3[0]; // unchecked
[Link](0); // bounds-checked (throws std::out_of_range)
[Link](); // first element
[Link](); // last element
// Capacity management
[Link](); // current number of elements
[Link](); // currently allocated slots (>= size)
[Link](100); // pre-allocate 100 slots – avoids re-allocations
v3.shrink_to_fit(); // release unused capacity
[Link](10); // change size (fills with 0 if growing)
[Link](); // remove all elements (capacity unchanged)
// Iteration
for(auto it = [Link](); it != [Link](); ++it) cout << *it;
for(int x : v3) cout << x; // range-based for
Internal working – Capacity doubling:
When capacity is full and push_back is called, vector: (1) allocates a new block of memory of double the old
capacity, (2) copies/moves all old elements there, (3) destroys the old memory. This is expensive but happens
rarely — hence amortised O(1).
■ Tip: Use reserve() when you know the final size to avoid repeated reallocations.
■ Warning: Iterators are invalidated after any reallocation (push_back may cause this). Do not hold
iterators across push_back calls unless capacity is reserved.
2.3 std::deque (Double-Ended Queue)
What it is: A sequence container allowing fast O(1) insertion and deletion at both the front and the back.
Internally implemented as a sequence of fixed-size chunks (not a single contiguous block), which is why front
insertion is cheap — it just prepends to the first chunk.
■ Analogy: A train with multiple carriages. You can quickly add carriages to the front or back. Accessing any
carriage by seat number is still fast (O(1)), but it's slightly slower than vector because carriages are not in one
continuous line.
#include <deque>
std::deque<int> dq = {3, 4, 5};
dq.push_front(2); dq.push_front(1); // {1,2,3,4,5}
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 5
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
dq.push_back(6); // {1,2,3,4,5,6}
dq.pop_front(); // {2,3,4,5,6}
dq.pop_back(); // {2,3,4,5}
dq[1]; // 3 – O(1) random access
[Link](1); // 3 – bounds-checked
[Link](); [Link]();
[Link]([Link]()+2, 99); // insert in middle – O(n)
Feature vector deque
Memory Single contiguous block Multiple fixed-size chunks
Front insert O(n) O(1)
Back insert O(1) amortised O(1)
Random access O(1) O(1) (slightly slower)
Cache performance Excellent Good
Use case Default choice Need fast front+back ops
2.4 std::list (Doubly Linked List)
What it is: A doubly linked list where each node contains: (1) the data, (2) a pointer to the next node, and (3) a
pointer to the previous node. There is no contiguous memory — each node is independently allocated on the
heap.
Key advantage: Inserting or removing elements at any position is O(1) — given an iterator to that position —
because only pointer re-wiring is needed, no shifting of elements.
■ Analogy: A chain of paper clips. Adding or removing a clip in the middle only requires opening two links — the
chain does not care how long it is. But to find the 50th clip, you must count from the start — no index shortcut.
#include <list>
std::list<int> lst = {10, 20, 30, 40, 50};
// Insertion & deletion
lst.push_front(5); // O(1)
lst.push_back(60); // O(1)
lst.pop_front(); // O(1)
lst.pop_back(); // O(1)
auto it = [Link]();
advance(it, 2); // move iterator 2 steps (O(n))
[Link](it, 99); // insert before it – O(1)
[Link](it); // erase at it – O(1)
// List-specific algorithms
[Link](); // O(n log n) – merge sort (stable)
[Link](); // O(n)
[Link](); // remove consecutive duplicates
[Link](20); // remove ALL nodes with value 20
lst.remove_if([](int x){ return x % 2 == 0; }); // remove evens
// Splice – move elements between lists in O(1)
std::list<int> other = {100, 200};
[Link]([Link](), other); // move entire other to front of lst
// Merge two sorted lists in O(n)
[Link](other);
■ Warning: std::list does NOT support random access. lst[3] will not compile. Use advance(it, n) to move
an iterator — which is O(n).
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 6
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
2.5 std::forward_list (C++11 – Singly Linked List)
What it is: A singly linked list — each node has only a pointer to the next node (no previous pointer). This
makes it more memory-efficient than std::list (saves one pointer per node) but restricts traversal to forward-only.
Design choice: std::forward_list is designed for embedded systems or situations where every byte counts. It
has no size() method (tracking size would require an extra integer).
#include <forward_list>
std::forward_list<int> fl = {10, 20, 30};
fl.push_front(5); // O(1) – only front insertion
fl.pop_front(); // O(1)
// insert_after / erase_after – operate AFTER the given position
auto it = fl.before_begin(); // sentinel before first element
fl.insert_after(it, 99); // insert 99 as first element
auto it2 = [Link]();
fl.erase_after(it2); // erase element AFTER it2
[Link](); [Link](); [Link]();
[Link](20);
2.6 Sequence Container Comparison Table
Container Random Access Front Insert Back Insert Mid Insert Memory Model Best Use Case
array O(1) N/A N/A N/A Stack contiguous Fixed compile-time array
vector O(1) O(n) O(1)* O(n) Heap contiguous General purpose, defaul
deque O(1) O(1) O(1) O(n) Heap segmented Front & back insertions
list O(n) O(1) O(1) O(1) Heap node-based Frequent mid-insertions
forward_list O(n) O(1) N/A O(1) Heap node-based Memory-critical, forward
■ Note: * amortised – O(n) when reallocation occurs
3. Container Adaptors
Container adaptors are wrappers that restrict the interface of an underlying sequence container to implement a
specific abstract data type. They do not expose iterators — access is strictly through their defined interface
(push, pop, top/front/back).
3.1 std::stack – LIFO (Last In, First Out)
What it is: A stack follows the LIFO principle: the last element pushed is the first to be popped. By default it
uses std::deque as its underlying container, but you can specify std::vector or std::list.
■ Analogy: A stack of plates in a cafeteria. You can only take the top plate (pop), or add a new plate on top
(push). You cannot reach a plate in the middle.
Applications: Function call stack, undo/redo in editors, expression evaluation, backtracking algorithms, syntax
checking (balanced brackets).
#include <stack>
std::stack<int> s;
[Link](10); [Link](20); [Link](30);
// Stack (top to bottom): 30 20 10
[Link](); // 30 – peek without removing
[Link](); // removes 30; top is now 20
[Link](); // 2
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 7
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
[Link](); // false
// With vector as underlying container
std::stack<int, std::vector<int>> vs;
// Classic balanced parentheses checker
string expr = '({[]})';
stack<char> stk;
for(char c : expr) {
if(c=='(' || c=='{' || c=='[') [Link](c);
else {
if([Link]()) { cout << 'Unbalanced'; break; }
[Link]();
}
}
if([Link]()) cout << 'Balanced';
3.2 std::queue – FIFO (First In, First Out)
What it is: A queue follows the FIFO principle: elements are inserted at the back (push/enqueue) and removed
from the front (pop/dequeue).
■ Analogy: A ticket queue at a cinema. The first person to join the queue is the first to be served. New people join
the back.
Applications: BFS (Breadth-First Search), CPU scheduling, print spooling, I/O request handling, message
passing systems.
#include <queue>
std::queue<string> q;
[Link]('Alice'); [Link]('Bob'); [Link]('Charlie');
// Queue (front to back): Alice Bob Charlie
[Link](); // 'Alice' – look at front
[Link](); // 'Charlie' – look at back
[Link](); // removes 'Alice'; front is now 'Bob'
[Link](); // 2
[Link](); // false
3.3 std::priority_queue
What it is: Elements are retrieved in priority order. By default, the highest value is at the top (max-heap). Uses
a binary heap internally (stored in std::vector). Insertion and removal are both O(log n).
■ Analogy: A hospital emergency room triage. The most critical patient (highest priority) is seen first, regardless
of arrival order.
#include <queue>
// Max-heap (default)
std::priority_queue<int> pq;
[Link](5); [Link](1); [Link](8); [Link](3);
[Link](); // 8 (maximum element)
[Link](); // removes 8; top is now 5
// Min-heap
std::priority_queue<int, std::vector<int>, std::greater<int>> minPQ;
[Link](5); [Link](1); [Link](3);
[Link](); // 1 (minimum element)
// Custom comparator (sort by second element of pair, ascending)
using P = pair<int,string>;
auto cmp = [](P a, P b){ return [Link] > [Link]; };
priority_queue<P, vector<P>, decltype(cmp)> custom_pq(cmp);
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 8
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
4. Associative Containers (Red-Black Tree Based)
Associative containers automatically maintain elements in sorted order. They are implemented internally using
a Red-Black Tree — a self-balancing Binary Search Tree (BST). All fundamental operations (insert, erase, find)
run in O(log n) time in both average and worst cases.
Red-Black Tree properties: Every node is red or black. The root is black. No two consecutive red nodes. Every
path from root to null has the same number of black nodes. These constraints ensure the tree height is always
O(log n).
4.1 std::set – Unique Sorted Elements
Stores unique elements in ascending order (by default). Duplicate insertions are silently ignored. Elements
cannot be modified in-place (they are effectively const) because changing an element would break the ordering.
#include <set>
std::set<int> s = {5, 1, 3, 1, 4, 2, 3};
// Stored as: {1, 2, 3, 4, 5} (sorted, duplicates removed)
[Link](6); // {1,2,3,4,5,6}
[Link](3); // no effect – 3 already exists
[Link](4); // remove 4
[Link](3); // 0 or 1 (set: always 0 or 1)
auto it = [Link](2); // iterator to 2, or [Link]() if not found
if(it != [Link]()) cout << 'Found: ' << *it;
// Range queries
s.lower_bound(3); // iterator to first element >= 3
s.upper_bound(3); // iterator to first element > 3
auto [lo, hi] = s.equal_range(3); // range of elements == 3
// Custom comparator (descending order)
std::set<int, std::greater<int>> desc_set = {1,5,3,2,4};
// Stored as: {5,4,3,2,1}
// Iterating
for(const int& x : s) cout << x << ' ';
4.2 std::multiset – Sorted with Duplicates
Exactly like std::set but allows duplicate elements. count() can return values greater than 1. erase(value)
removes ALL occurrences; to erase only one, use erase(find(value)).
std::multiset<int> ms = {1, 2, 2, 3, 3, 3, 4};
[Link](3); // 3
[Link](5); // 0
[Link](2); // removes BOTH 2s
[Link]([Link](3)); // removes only ONE 3
// Iterate: 1 3 3 4 (after above erases)
for(int x : ms) cout << x << ' ';
4.3 std::map – Key-Value Pairs, Unique Keys, Sorted
A sorted associative container that stores key-value pairs. Each key is unique. Keys are sorted in ascending
order (by default). Accessing a non-existent key with operator[] creates it with a default value — use find() or at()
to avoid accidental insertion.
■ Analogy: A dictionary. Words (keys) are sorted alphabetically, each with a unique definition (value). Looking up
a word takes O(log n) time.
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 9
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
#include <map>
std::map<std::string, int> grades;
// Insertion
grades['Alice'] = 92;
grades['Bob'] = 85;
[Link]({'Charlie', 78});
[Link]('Diana', 95); // construct in-place
// Access
grades['Alice']; // 92
[Link]('Bob'); // 85 – throws if key not found
grades['Zoe']; // Creates 'Zoe' with value 0 !
// Safe check
if([Link]('Charlie') != [Link]())
cout << grades['Charlie'];
[Link]('Alice'); // 1 if exists, 0 otherwise
// Modification
[Link]('Bob');
grades['Alice'] = 99; // update existing
// Iteration – structured bindings (C++17)
for(auto& [key, val] : grades)
cout << key << ': ' << val << endl;
// Try insert (insert only if key not present)
auto [it, success] = grades.try_emplace('Alice', 100);
// success = false (Alice already exists)
■ Warning: grades['Zoe'] creates a new entry with value 0 even if you only intended to read! Always use
find() or count() for safe lookup.
4.4 std::multimap – Key-Value Pairs, Duplicate Keys
Like std::map but allows multiple values for the same key. operator[] is NOT available (ambiguous which
value to return). Use insert() and equal_range() to manage multi-valued keys.
#include <map>
std::multimap<string, int> scores;
[Link]({'Math', 90});
[Link]({'Math', 85}); // duplicate key OK
[Link]({'Science', 88});
[Link]('Math'); // 2
// Get all values for key 'Math'
auto range = scores.equal_range('Math');
for(auto it = [Link]; it != [Link]; ++it)
cout << it->first << ': ' << it->second << endl;
5. Unordered (Hash-Based) Containers
Unordered containers use a Hash Table internally. A hash function maps each key to a bucket index. Elements
in the same bucket are stored in a linked list (chaining). Elements are not stored in any sorted order.
Time complexity: Average case O(1) for insert, find, erase. Worst case O(n) when many keys hash to the
same bucket (hash collision). In practice, a good hash function makes worst case rare.
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 10
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
■ Analogy: A library with 26 shelves labelled A-Z. A book is placed on the shelf matching its first letter. Finding
'Harry Potter' means going directly to shelf H (O(1)) — but if hundreds of books start with H, searching that shelf
is slow.
5.1 Comparison: Ordered vs Unordered
Property set / map (Ordered) unordered_set / map (Unordered)
Internal Structure Red-Black Tree Hash Table
Element Order Sorted No defined order
Average Find/Insert O(log n) O(1)
Worst Case O(log n) O(n) (hash collision)
Memory Lower Higher (buckets + load factor)
Custom keys need operator< hash function + operator==
Iteration order Sorted Undefined
5.2 unordered_set & unordered_multiset
#include <unordered_set>
std::unordered_set<int> us = {3, 1, 4, 1, 5, 9};
// {3,1,4,5,9} – duplicates removed, order undefined
[Link](2);
[Link](4); // 1 (present) or 0 (absent)
[Link](4) != [Link](); // true
[Link](5);
// Hash table statistics
us.bucket_count(); // number of buckets
us.load_factor(); // elements / buckets
us.max_load_factor(0.75); // rehash when load exceeds 0.75
[Link](20); // force resize to at least 20 buckets
// unordered_multiset allows duplicates
std::unordered_multiset<int> ums = {1,2,2,3,3,3};
[Link](3); // 3
5.3 unordered_map & unordered_multimap
#include <unordered_map>
std::unordered_map<string, int> word_count;
word_count['apple']++;
word_count['banana']++;
word_count['apple']++; // apple = 2
word_count.at('apple'); // 2
word_count.count('mango'); // 0
for(auto& [word, cnt] : word_count)
cout << word << ': ' << cnt << endl;
// Custom hash for pair<int,int>
struct PairHash {
size_t operator()(const pair<int,int>& p) const {
return hash<int>()([Link]) ^ (hash<int>()([Link]) << 32);
}
};
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 11
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
unordered_map<pair<int,int>, int, PairHash> grid;
grid[{0,0}] = 1;
6. STL Algorithms (#include <algorithm>)
STL algorithms are standalone template functions that operate on ranges defined by iterators [first, last). They
work on any container that provides the required iterator category. They follow the principle of generic
programming: write the algorithm once, use it with any data structure.
6.1 Non-Modifying Algorithms
vector<int> v = {1,2,3,4,5,4,3};
// find – returns iterator to first match, or end()
auto it = find([Link](), [Link](), 4); // points to first 4
// find_if – find based on condition
auto it2 = find_if([Link](), [Link](), [](int x){ return x>3; });
// count & count_if
count([Link](), [Link](), 3); // 2
count_if([Link](), [Link](), [](int x){ return x%2==0; }); // 2
// all_of, any_of, none_of (C++11)
all_of([Link](),[Link](),[](int x){return x>0;}); // true
any_of([Link](),[Link](),[](int x){return x==5;}); // true
none_of([Link](),[Link](),[](int x){return x<0;}); // true
// for_each
for_each([Link](), [Link](), [](int& x){ x *= 2; });
6.2 Modifying Algorithms
vector<int> src = {1,2,3,4,5};
vector<int> dst(5);
// copy – copy elements to destination
copy([Link](), [Link](), [Link]());
// copy_if – copy only elements satisfying predicate
vector<int> evens;
copy_if([Link](),[Link](),back_inserter(evens),[](int x){return x%2==0;});
// transform – apply function element-wise
transform([Link](),[Link](),[Link](),[](int x){ return x*x; });
// dst = {1,4,9,16,25}
// fill & fill_n
fill([Link](), [Link](), 0); // fill all with 0
fill_n([Link](), 3, 7); // fill first 3 with 7
// replace & replace_if
replace([Link](),[Link](), 3, 99); // replace 3 with 99
replace_if([Link](),[Link](),[](int x){return x>3;},0);
// remove & remove_if (does NOT erase from container!)
auto new_end = remove([Link](),[Link](), 99);
[Link](new_end, [Link]()); // erase-remove idiom
■ Note: remove() does NOT shrink the vector. It shifts elements and returns the new logical end. Always pair with
erase() – the erase-remove idiom.
6.3 Sorting Algorithms
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 12
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
vector<int> v = {5,3,8,1,9,2,7};
sort([Link](), [Link]()); // ascending O(n log n)
sort([Link](), [Link](), greater<int>()); // descending
sort([Link](), [Link](), [](int a, int b){ return abs(a) < abs(b); }); // custom
stable_sort([Link](), [Link]()); // preserves relative order of equals
// partial_sort – only sort first k elements
partial_sort([Link](), [Link]()+3, [Link]()); // smallest 3 at front
// nth_element – guarantee nth element is what would be there if sorted
nth_element([Link](), [Link]()+4, [Link]()); // 5th smallest at [4]
is_sorted([Link](), [Link]()); // check if sorted
6.4 Searching & Set Algorithms
// Binary search – REQUIRES sorted range
vector<int> sv = {1,2,3,4,5,6,7,8,9};
binary_search([Link](), [Link](), 5); // true (O(log n))
lower_bound([Link](), [Link](), 5); // iterator to first >= 5
upper_bound([Link](), [Link](), 5); // iterator to first > 5
// Set operations (both ranges must be sorted)
vector<int> a={1,2,3,4}, b={3,4,5,6}, result;
set_union([Link](),[Link](),[Link](),[Link](),back_inserter(result));
// result = {1,2,3,4,5,6}
set_intersection([Link](),[Link](),[Link](),[Link](),back_inserter(result));
// result = {3,4}
set_difference([Link](),[Link](),[Link](),[Link](),back_inserter(result));
// result = {1,2}
6.5 Numeric Algorithms (#include <numeric>)
#include <numeric>
vector<int> v = {1,2,3,4,5};
accumulate([Link](), [Link](), 0); // 15 (sum)
accumulate([Link](), [Link](), 1, multiplies<int>()); // 120 (product)
inner_product([Link](),[Link](),[Link](),0); // dot product = 55
vector<int> prefix(5);
partial_sum([Link](),[Link](),[Link]()); // {1,3,6,10,15}
iota([Link](), [Link](), 1); // fill with 1,2,3,4,5
7. Iterators
Iterators are objects that act like smart pointers to elements in a container. They provide a uniform interface to
traverse different containers, hiding each container's internal implementation details.
The iterator concept forms a hierarchy — each category supports all operations of the categories below it.
Category Direction Read/Write Arithmetic Containers Examples
Input Forward Read-only ++ istream istream_iterator
Output Forward Write-only ++ ostream ostream_iterator, back_inserter
Forward Forward Read+Write ++ forward_list,unordered_* forward_list::iterator
Bidirectional Both Read+Write ++,-- list,set,map list::iterator
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 13
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
Category Direction Read/Write Arithmetic Containers Examples
Random Access Both Read+Write ++,--,+,-,[] vector,deque,array vector::iterator
Contiguous (C++17) Both Read+Write all+ptr arith vector,array,string vector::iterator
7.1 Iterator Operations
vector<int> v = {10,20,30,40,50};
auto it = [Link](); // points to 10
*it; // dereference: 10
++it; // advance: now points to 20
it++; // post-increment (prefer pre)
--it; // go back: 20 again
it += 3; // jump 3 forward (random access only)
it -= 1; // jump back (random access only)
it[1]; // same as *(it+1) (random access only)
// Distance between iterators
distance([Link](), [Link]()); // 5
// Advance iterator by n steps (works for all iterator types)
auto it2 = [Link]();
advance(it2, 3); // now points to 40
// next() and prev() – return new iterator without modifying original
auto it3 = next([Link](), 2); // points to 30
auto it4 = prev([Link](), 1); // points to 50
7.2 Reverse Iterators
for(auto it = [Link](); it != [Link](); ++it)
cout << *it << ' '; // 50 40 30 20 10
// rbegin() = iterator to last element
// rend() = iterator before first element
7.3 Iterator Adaptors
// back_inserter – calls push_back on each assignment
vector<int> src={1,2,3}, dst;
copy([Link](), [Link](), back_inserter(dst)); // dst={1,2,3}
// front_inserter – calls push_front (for deque/list)
deque<int> dq;
copy([Link](),[Link](),front_inserter(dq)); // dq={3,2,1}
// inserter – inserts at specific position
set<int> s;
copy([Link](),[Link](), inserter(s, [Link]()));
// istream_iterator – read from input stream
istream_iterator<int> in_it(cin), eof;
vector<int> data(in_it, eof); // reads all ints until EOF
// ostream_iterator – write to output stream
ostream_iterator<int> out_it(cout, ', ');
copy([Link](), [Link](), out_it); // prints: 1, 2, 3,
8. Functors (Function Objects)
A functor is any object that overloads operator(), making it callable like a function. Functors are preferred over
plain functions in STL algorithms because they can hold state and are typically inlined by the compiler (better
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 14
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
performance).
// Built-in functors from <functional>
sort([Link](),[Link](), greater<int>()); // descending
sort([Link](),[Link](), less<int>()); // ascending (default)
// Custom stateful functor
struct Multiplier {
int factor;
Multiplier(int f) : factor(f) {}
int operator()(int x) const { return x * factor; }
};
Multiplier times3(3);
transform([Link](),[Link](),[Link](), times3);
// std::function – wrapper for any callable
#include <functional>
function<int(int,int)> add = [](int a, int b){ return a+b; };
add(3, 4); // 7
// std::bind – partial application
auto add5 = bind(add, placeholders::_1, 5);
add5(10); // 15
9. File Handling in C++
C++ provides file I/O through the <fstream> header. File streams are objects that represent files; reading/writing
works exactly like cin/cout but directed to files. Three main classes:
■ ifstream: Input file stream – read from file
■ ofstream: Output file stream – write to file
■ fstream: Bidirectional – both read and write
9.1 Opening & Closing Files
#include <fstream>
#include <iostream>
#include <string>
// Method 1: open in constructor
ofstream outFile('[Link]'); // opens for writing
// Method 2: open() method
ofstream outFile2;
[Link]('[Link]', ios::app); // open in append mode
// Always check if open succeeded
if(!outFile.is_open()) {
cerr << 'Error opening file!' << endl;
return 1;
}
outFile << 'Hello, File!' << endl;
outFile << 42 << ' ' << 3.14 << endl;
[Link](); // flush & close
// Reading line by line
ifstream inFile('[Link]');
string line;
while(getline(inFile, line)) {
cout << line << endl;
}
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 15
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
[Link]();
9.2 File Open Modes
Mode Meaning Behaviour if file exists / doesn't exist
ios::in Read Open for reading / fail if not found
ios::out Write Truncate to zero / create new
ios::app Append All writes at end / create new
ios::ate At end Open & seek to end, can write anywhere / create
ios::trunc Truncate Erase all content then open
ios::binary Binary No newline translation
// Combining modes
fstream f('[Link]', ios::in | ios::out | ios::app);
fstream bf('[Link]', ios::in | ios::out | ios::binary);
9.3 Reading Different Data Types
ifstream f('[Link]');
int n; double d; string s;
f >> n >> d >> s; // read whitespace-separated values
getline(f, s); // read entire line including spaces
// Read character by character
char c;
while([Link](c)) cout << c;
// Read entire file into string
ifstream file('[Link]');
string content((istreambuf_iterator<char>(file)),
istreambuf_iterator<char>());
9.4 Binary File I/O
Binary files store data in the exact binary representation used by the CPU — no text conversion. This is faster
and more compact for structured data like structs.
struct Student {
int roll;
char name[30];
float marks;
};
// Write binary
Student s1 = {101, 'Alice', 95.5f};
ofstream bw('[Link]', ios::binary);
[Link](reinterpret_cast<const char*>(&s1), sizeof(s1));
[Link]();
// Read binary
Student s2;
ifstream br('[Link]', ios::binary);
[Link](reinterpret_cast<char*>(&s2), sizeof(s2));
cout << [Link] << ' ' << [Link] << ' ' << [Link];
[Link]();
// Write/read multiple records
vector<Student> students = {{102,'Bob',88.5f},{103,'Carol',92.0f}};
ofstream bw2('[Link]', ios::binary);
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 16
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
for(auto& s : students)
[Link](reinterpret_cast<const char*>(&s), sizeof(s));
9.5 File Pointer / Random Access
seekg (get pointer) and seekp (put pointer) allow jumping to any position in the file, enabling random access to
records without reading the whole file.
fstream f('[Link]', ios::in|ios::out|ios::binary);
// Seek to beginning
[Link](0, ios::beg);
// Seek to end, find file size
[Link](0, ios::end);
streampos size = [Link]();
int numRecords = size / sizeof(Student);
// Seek to specific record (e.g., record index 2)
[Link](2 * sizeof(Student), ios::beg);
Student s;
[Link](reinterpret_cast<char*>(&s), sizeof(s));
// seekg positions: ios::beg, ios::cur, ios::end
[Link](10, ios::cur); // move forward 10 bytes from current
[Link](-5, ios::end); // 5 bytes before end
// tellg / tellp – current position
streampos pos = [Link]();
9.6 Error Handling in File I/O
ifstream f('[Link]');
if(!f) { cerr << 'Cannot open!'; return; }
[Link](); // true if no errors
[Link](); // true if end of file reached
[Link](); // true if logical error (bad format)
[Link](); // true if fatal I/O error
[Link](); // clear error flags
// Exception mode (optional)
[Link](ifstream::failbit | ifstream::badbit);
try { /* file ops */ }
catch(const ios_base::failure& e) { cerr << [Link](); }
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 17
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
UNIT V
Lambda, Concurrency, Smart Pointers, rvalue & Templates
10. Exception Handling
An exception is an event that occurs during program execution that disrupts the normal flow of instructions.
Exception handling separates error detection (throw) from error recovery (catch), making code cleaner and
more robust.
Without exception handling, every function would need to return error codes and every caller would need to
check them — deeply nested and error-prone. C++ exceptions automatically unwind the call stack until a
matching handler is found.
10.1 try – throw – catch Mechanism
try block: Code that might throw is placed here.
throw: Signals that an error has occurred; throws an exception object.
catch block: Handles a specific type of exception.
#include <stdexcept>
#include <iostream>
double divide(double a, double b) {
if(b == 0.0)
throw std::invalid_argument('Divisor cannot be zero');
return a / b;
}
int main() {
try {
double result = divide(10.0, 0.0); // throws
cout << result; // never reached
}
catch(const std::invalid_argument& e) {
cerr << 'Invalid argument: ' << [Link]() << endl;
}
catch(const std::exception& e) { // catch-all std exceptions
cerr << 'Exception: ' << [Link]() << endl;
}
catch(...) { // catch anything (use sparingly)
cerr << 'Unknown exception!' << endl;
}
return 0;
}
■ Note: Catch blocks are checked in order. The first matching catch is executed. Place more specific exceptions
before general ones.
10.2 Stack Unwinding
When an exception is thrown, C++ automatically unwinds the call stack: destructors of all local objects in the
current and enclosing scopes are called in reverse order of construction. This ensures RAII-managed resources
are properly released even when exceptions occur.
class Resource {
public:
Resource() { cout << 'Resource acquired\n'; }
~Resource() { cout << 'Resource released\n'; } // called on unwind!
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 18
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
};
void risky() {
Resource r; // RAII object
throw runtime_error('Something went wrong');
// r's destructor is called automatically before exception propagates
}
try { risky(); }
catch(exception& e) { cout << [Link](); }
10.3 Standard Exception Hierarchy
std::exception (base – has what())
■■■ logic_error (programming errors – detectable before run)
■ ■■■ invalid_argument (bad function argument)
■ ■■■ domain_error (math domain error e.g. sqrt(-1))
■ ■■■ length_error (exceeded max length)
■ ■■■ out_of_range (e.g. vector::at())
■■■ runtime_error (errors detectable only at run time)
■ ■■■ range_error
■ ■■■ overflow_error (arithmetic overflow)
■ ■■■ underflow_error
■■■ bad_alloc (new fails – out of memory)
■■■ bad_cast (dynamic_cast fails on reference)
■■■ bad_typeid (typeid on null pointer)
■■■ bad_exception (unexpected exception type)
10.4 Custom Exception Classes
#include <stdexcept>
class InsufficientFundsException : public std::exception {
double amount_, balance_;
string msg_;
public:
InsufficientFundsException(double amt, double bal)
: amount_(amt), balance_(bal) {
msg_ = 'Tried to withdraw ' + to_string(amt)
+ ' but balance is ' + to_string(bal);
}
const char* what() const noexcept override { return msg_.c_str(); }
double shortfall() const { return amount_ - balance_; }
};
void withdraw(double amt, double& balance) {
if(amt > balance) throw InsufficientFundsException(amt, balance);
balance -= amt;
}
try { withdraw(500.0, bal); }
catch(const InsufficientFundsException& e) {
cout << [Link]() << endl;
cout << 'Shortfall: ' << [Link]();
}
10.5 noexcept Specifier & Operator
noexcept on a function declaration means: 'I guarantee this function will never throw an exception.' If it does,
std::terminate() is called immediately (no stack unwinding). This enables compiler optimisations.
void safe() noexcept { /* guaranteed not to throw */ }
void maybe() noexcept(false) { /* may throw */ }
// Conditional noexcept
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 19
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
// noexcept operator – compile-time check
static_assert(noexcept(safe()), 'safe must not throw');
■ Tip: Always mark move constructors and move assignment operators as noexcept. This allows STL containers
to use move operations during reallocation.
11. Concurrency in C++11
Concurrency allows multiple threads of execution to run simultaneously (or appear to). C++11 introduced a
portable, hardware-agnostic concurrency model with std::thread, std::mutex, std::atomic, std::future, and
std::condition_variable.
■ Analogy: A restaurant kitchen. The head chef (main thread) can assign tasks to sous-chefs (worker threads).
Each works independently, but they must coordinate (mutex) when sharing the same pan (shared resource) to
avoid disasters.
11.1 std::thread
#include <thread>
#include <iostream>
void worker(int id, const string& msg) {
cout << 'Thread ' << id << ': ' << msg << endl;
}
int main() {
// Create threads – execution starts immediately
thread t1(worker, 1, 'Hello');
thread t2(worker, 2, 'World');
// Using lambda
thread t3([](){ cout << 'Lambda thread' << endl; });
// join – main blocks until thread finishes
[Link](); [Link](); [Link]();
// detach – thread runs independently (daemon)
thread t4(worker, 4, 'Daemon');
[Link](); // do NOT join after detach
// Thread utilities
cout << thread::hardware_concurrency(); // logical CPU cores
return 0;
}
■ Warning: Every thread must be either join()ed or detach()ed before it is destroyed. Failing to do so
causes std::terminate() and program crash.
11.2 Race Conditions & Data Races
A data race occurs when two threads access the same memory location concurrently and at least one access is
a write, without synchronisation. This leads to undefined behaviour — results may be wrong and
non-deterministic.
// BAD – data race!
int counter = 0;
void bad_increment() {
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 20
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
counter++; // read-modify-write is NOT atomic!
}
thread t1(bad_increment); // both threads race on counter
thread t2(bad_increment); // result could be 1 or 2
[Link](); [Link]();
// Expected: 2 | Actual: could be 1 due to race
11.3 std::mutex – Mutual Exclusion
A mutex ensures that only one thread executes a critical section at a time. A thread calls lock() to acquire the
mutex; if already locked, it blocks until the mutex is released.
#include <mutex>
mutex mtx;
int counter = 0;
void safe_increment() {
[Link](); // block if already locked
++counter; // critical section
[Link](); // release
}
// PREFERRED: lock_guard – RAII mutex (auto-unlocks)
void safer_increment() {
lock_guard<mutex> guard(mtx); // locks on construction
++counter;
// guard destructor unlocks automatically – even if exception thrown
}
// unique_lock – more flexible (can defer, unlock early, timed)
void flexible_increment() {
unique_lock<mutex> ulock(mtx);
++counter;
[Link](); // optional early unlock
// ... do non-critical work ...
[Link](); // re-lock if needed
}
Mutex Type Key Feature
mutex Basic, non-recursive
recursive_mutex Same thread can lock multiple times
timed_mutex try_lock_for(duration), try_lock_until(time_point)
shared_mutex (C++17) Multiple readers OR one writer (readers-writer lock)
shared_timed_mutex shared_mutex + timed operations
// shared_mutex – many readers, one writer
#include <shared_mutex>
shared_mutex rw_mutex;
// Reader thread
void reader() {
shared_lock<shared_mutex> lock(rw_mutex); // multiple allowed
// ... read shared data ...
}
// Writer thread
void writer() {
unique_lock<shared_mutex> lock(rw_mutex); // exclusive
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 21
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// ... modify shared data ...
}
■ Warning: Deadlock occurs when thread A holds mutex X and waits for Y, while thread B holds Y and
waits for X. Always lock mutexes in the same order. Use std::scoped_lock (C++17) to lock multiple mutexes
safely.
11.4 std::atomic – Lock-Free Thread Safety
std::atomic wraps a value and guarantees that all operations on it are atomic — indivisible and visible to all
threads without explicit locking. Ideal for simple counters, flags, and state variables.
#include <atomic>
atomic<int> count(0);
void increment() {
++count; // atomic increment (no mutex needed)
count.fetch_add(1); // same as above, more explicit
count.fetch_sub(1); // atomic decrement
}
// Atomic store/load
[Link](0); // atomic write
int val = [Link](); // atomic read
// Compare-and-swap (CAS) – foundation of lock-free algorithms
int expected = 5;
count.compare_exchange_strong(expected, 10);
// If count==5, set count=10 and return true
// If count!=5, set expected=count and return false
// Atomic flag – simplest atomic type
atomic_flag flag = ATOMIC_FLAG_INIT;
flag.test_and_set(); // atomically set to true, returns previous
[Link](); // atomically clear
// Memory ordering (advanced)
[Link](1, memory_order_release);
[Link](memory_order_acquire);
11.5 std::condition_variable
A condition variable allows threads to wait for a condition to become true, without busy-waiting (spinning). One
thread waits; another notifies when the condition changes. Must be used with a unique_lock.
#include <condition_variable>
#include <mutex>
#include <thread>
mutex mtx;
condition_variable cv;
bool data_ready = false;
int shared_data = 0;
// Producer thread
void producer() {
this_thread::sleep_for(chrono::milliseconds(100));
{
lock_guard<mutex> lock(mtx);
shared_data = 42;
data_ready = true;
}
cv.notify_one(); // wake ONE waiting thread
// cv.notify_all() – wake ALL waiting threads
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 22
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// Consumer thread
void consumer() {
unique_lock<mutex> lock(mtx);
[Link](lock, []{ return data_ready; }); // wait with predicate
// Predicate prevents spurious wakeups
cout << 'Got data: ' << shared_data;
}
11.6 std::future & std::async
std::async launches a function asynchronously and returns a std::future that holds the result. [Link]() blocks
until the result is ready.
#include <future>
int heavy_computation(int n) {
this_thread::sleep_for(chrono::seconds(2));
return n * n;
}
// Launch asynchronously
future<int> f = async(launch::async, heavy_computation, 10);
cout << 'Doing other work...\n';
int result = [Link](); // blocks until ready (returns 100)
cout << 'Result: ' << result;
12. Lambda Expressions (C++11)
A lambda expression defines an anonymous, inline function object. It is syntactic sugar for creating a
temporary functor class. Lambdas are especially useful when passing short callbacks to STL algorithms,
threads, and event handlers.
What the compiler does: When you write a lambda, the compiler creates an unnamed class (closure type) with
an operator() and a constructor that captures the specified variables. The lambda expression evaluates to an
object of this closure type.
12.1 Complete Lambda Syntax
[ capture_list ] ( parameter_list ) mutable -> return_type
{ function_body }
// All parts are optional (except [] and {})
[]{ } // minimal lambda (no-op)
[](int x){ return x*2; } // with parameter
[](int x) -> double { return x*2; }// explicit return type
[x](){ return x; } // capture x by value
[&x](){ x++; } // capture x by reference
[=](){ return x+y; } // capture all by value
[&](){ x++; y++; } // capture all by reference
[x, &y](){ return x + y; } // mixed capture
[=, &x](){ return y + x; } // all value, x by reference
12.2 Capture Modes In Depth
Capture What is captured Modifiable? Notes
[] Nothing – Cannot access outer variables
[=] All locals by value No Copies made at lambda creation
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 23
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
Capture What is captured Modifiable? Notes
[&] All locals by reference Yes Careful: dangling reference if lambda outlives scope
[x] Only x by value No x is a copy; outer x unchanged
[&x] Only x by reference Yes Changes inside affect outer x
[this] Current object's this pointer Yes Access member variables
[*this] Copies the whole object (C++17)
No Safe when object may be destroyed
int base = 10, factor = 3;
// Capture by value – lambda has its OWN copy of base
auto f1 = [base](int x){ return x + base; };
base = 999; // does NOT affect f1's captured copy
f1(5); // still returns 15
// Capture by reference – lambda sees live base
auto f2 = [&base](int x){ return x + base; };
f2(5); // returns 1004 (5 + 999)
// mutable – allows modifying captured-by-value copy
auto counter = [count=0]() mutable {
return ++count; // count is lambda's own copy
};
counter(); // 1
counter(); // 2
counter(); // 3
12.3 Lambdas with STL Algorithms
vector<int> v = {3,1,4,1,5,9,2,6};
// Sort descending
sort([Link](), [Link](), [](int a, int b){ return a > b; });
// Find first element > 4
auto it = find_if([Link](), [Link](), [](int x){ return x > 4; });
// Remove all negatives
[Link](remove_if([Link](),[Link](),[](int x){return x<0;}),[Link]());
// Transform – square all elements
transform([Link](),[Link](),[Link](),[](int x){return x*x;});
// Stateful: capture sum
int total = 0;
for_each([Link](),[Link](),[&total](int x){ total+=x; });
cout << 'Sum: ' << total;
12.4 Generic (Polymorphic) Lambdas (C++14)
In C++14, lambda parameters can be auto, making the lambda a function template that works with any type.
// Generic lambda
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // int + int = 3
add(1.5, 2.5); // double + double = 4.0
add(string('Hi'), string(' there')); // string concat
// Generic lambda with STL
auto print = [](const auto& x) { cout << x << ' '; };
vector<int> vi = {1,2,3};
vector<string> vs = {'a','b','c'};
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 24
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
for_each([Link](),[Link](),print); // works for both
for_each([Link](),[Link](),print);
12.5 Lambda as Return Type & Stored in Variables
// Store lambda in auto variable
auto greet = [](const string& name){ cout << 'Hello ' << name; };
greet('Alice');
// Store lambda in std::function (type-erased)
#include <functional>
function<int(int)> f = [](int x){ return x*2; };
// Return lambda from function
function<int(int)> make_adder(int n) {
return [n](int x){ return x + n; };
}
auto add10 = make_adder(10);
add10(5); // 15
13. Smart Pointers (#include <memory>)
Smart pointers are class templates that wrap raw pointers and automatically manage the lifetime of
heap-allocated objects. They implement RAII (Resource Acquisition Is Initialisation) — the resource is released
when the smart pointer goes out of scope, even if an exception is thrown.
■ Analogy: A raw pointer is like a library book — you must remember to return it (delete) or you've lost it forever
(memory leak). A smart pointer is like a library book with an automatic return system — it goes back when you're
done.
Problems smart pointers solve:
■ Memory leaks – forgetting to call delete
■ Dangling pointers – using memory after it's freed
■ Double-delete – calling delete twice on same pointer
■ Exception safety – delete not called when exception unwinds stack
13.1 std::unique_ptr – Exclusive Ownership
Semantics: At any moment, exactly ONE unique_ptr owns the resource. When the unique_ptr is destroyed (or
reset), the resource is deleted. Cannot be copied — only moved. This enforces clear, single ownership.
#include <memory>
// Creation – use make_unique (C++14, preferred)
auto p1 = make_unique<int>(42); // new int(42)
auto p2 = make_unique<string>('Hello');
auto p3 = make_unique<int[]>(10); // array of 10 ints
// Usage
*p1; // dereference: 42
p2->size(); // member access: 5
// Transfer ownership (move)
auto p4 = move(p1); // p1 is now null; p4 owns the int
p1 == nullptr; // true
// Release & reset
int* raw = [Link](); // p4 is null; raw is a raw pointer (YOU manage it)
delete raw; // must manually delete released pointer
[Link](); // destroy string, p2 = null
[Link](new string('World')); // p2 now owns a new string
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 25
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// In containers
vector<unique_ptr<int>> vec;
vec.push_back(make_unique<int>(1));
vec.emplace_back(make_unique<int>(2));
// Custom deleter
auto file_deleter = [](FILE* f){ if(f) fclose(f); };
unique_ptr<FILE, decltype(file_deleter)> file(fopen('[Link]','r'), file_deleter);
13.2 std::shared_ptr – Shared Ownership
Semantics: Multiple shared_ptrs can own the same resource simultaneously. Internally uses a control block
containing a reference count. When the count drops to zero (last shared_ptr is destroyed), the resource is
deleted.
auto sp1 = make_shared<int>(100); // count = 1
auto sp2 = sp1; // count = 2 (copy!)
auto sp3 = sp1; // count = 3
sp1.use_count(); // 3
*sp1; // 100
*sp2; // 100 (same object)
[Link](); // count = 2
[Link](); // count = 1
[Link](); // count = 0 → int(100) is deleted
// shared_ptr with custom class
struct Node {
int val;
shared_ptr<Node> next;
};
auto head = make_shared<Node>();
head->val = 1;
head->next = make_shared<Node>();
head->next->val = 2;
■ Warning: Circular references with shared_ptr cause memory leaks! If A owns B and B owns A, the
reference count never reaches zero. Use weak_ptr to break cycles.
13.3 std::weak_ptr – Non-Owning Observer
Semantics: weak_ptr observes a shared_ptr's resource without contributing to the reference count. It cannot
access the resource directly — you must lock() it to get a shared_ptr, which may be null if the object was
deleted.
auto sp = make_shared<int>(42);
weak_ptr<int> wp = sp;
sp.use_count(); // 1 (weak_ptr does NOT increase count)
wp.use_count(); // 1
[Link](); // false (sp still alive)
// Must lock to access
if(auto locked = [Link]()) { // locked is a shared_ptr
cout << *locked; // 42
} else {
cout << 'Object destroyed';
}
[Link](); // destroy object, count = 0
[Link](); // true
[Link]() == nullptr; // true
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 26
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// Breaking circular reference
struct Node {
shared_ptr<Node> next;
weak_ptr<Node> prev; // weak_ptr breaks the cycle!
};
Smart Pointer Ownership Copy Use Count Affect Use When
unique_ptr Exclusive – 1 owner No (move only) N/A Default – single owner
shared_ptr Shared – N owners Yes Yes (increment) Need multiple owners
weak_ptr None – observer Yes No Observe; break cycles
14. lvalue, rvalue & Perfect Forwarding
Every C++ expression has both a type and a value category. Value categories determine how the expression
can be used — whether it can appear on the left of an assignment, whether it can be moved from, etc.
14.1 Value Categories
Category Has Identity? Can be moved? Example
lvalue Yes No Named variable: int x = 5; → x
xvalue Yes Yes std::move(x), returned rvalue ref
prvalue No Yes Literal: 42, 3.14, 'hello', x+y
glvalue (=lvalue+xvalue) Yes – Any expression with identity
rvalue (=xvalue+prvalue) – Yes Temporary / movable
int x = 10; // x is lvalue; 10 is prvalue
int& lref = x; // lvalue reference – binds to lvalue
int&& rref = 42; // rvalue reference – binds to prvalue
int&& xref = std::move(x); // xvalue – x is now 'about to die'
// lvalue cannot bind to rvalue reference
// int&& bad = x; // COMPILE ERROR
// const lvalue ref can bind to anything
const int& cref = 42; // OK – extends lifetime of temporary
14.2 Move Semantics – std::move
Move semantics allow transferring a resource from one object to another without copying. For large objects
(vector, string, unique_ptr), this is O(1) instead of O(n). std::move is a cast — it marks an object as 'safe to steal
from'.
#include <utility>
// Move constructor
class BigData {
int* data; int size;
public:
BigData(int n) : data(new int[n]), size(n) {}
// Copy constructor – O(n)
BigData(const BigData& other) : size([Link]) {
data = new int[size];
copy([Link], [Link]+size, data);
}
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 27
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// Move constructor – O(1)
BigData(BigData&& other) noexcept
: data([Link]), size([Link]) {
[Link] = nullptr; // steal the resource
[Link] = 0;
}
~BigData() { delete[] data; }
};
BigData a(1000);
BigData b = move(a); // O(1) – no copy; a is empty
// vector uses move during reallocation (if noexcept)
vector<BigData> vec;
vec.push_back(move(a)); // moves 'a' into vector
14.3 Universal References & Perfect Forwarding
A forwarding reference (also called universal reference) has the form T&& in a template context. It can bind to
both lvalues and rvalues. std::forward<T>(arg) preserves the value category — forwarding an lvalue as an
lvalue and an rvalue as an rvalue.
#include <utility>
// Without perfect forwarding – always copies (inefficient)
void bad_wrapper(int x) { process(x); }
// With perfect forwarding – preserves value category
template<typename T>
void wrapper(T&& arg) {
// If arg was lvalue → forward as lvalue
// If arg was rvalue → forward as rvalue
process(std::forward<T>(arg));
}
int x = 5;
wrapper(x); // T=int&, forward as lvalue
wrapper(42); // T=int, forward as rvalue
wrapper(move(x)); // T=int, forward as rvalue
// make_unique uses perfect forwarding
// emplace_back uses perfect forwarding
vector<pair<int,string>> v;
v.emplace_back(1, 'Alice'); // constructs pair directly in vector
■ Note: std::move unconditionally casts to rvalue. std::forward conditionally casts to rvalue only if the input was an
rvalue. Always use forward in templates.
15. Templates in C++
Templates are C++'s mechanism for generic programming. They allow writing code that works with any type
— the compiler generates type-specific code at compile time (instantiation). This gives both generality and
performance (zero runtime overhead).
■ Analogy: A template is like a cookie cutter. The same cutter (template) can stamp out cookies of any material
(int, double, string) — but each resulting cookie is separate and specific.
15.1 Function Templates
// Basic function template
template<typename T>
T max_val(T a, T b) { return (a > b) ? a : b; }
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 28
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
max_val(3, 7); // T=int
max_val(3.14, 2.71); // T=double
max_val<string>('abc','xyz'); // explicit instantiation
// Multiple type parameters
template<typename T, typename U>
auto add(T a, U b) { return a + b; }
// Non-type template parameter
template<int N>
int power_of_two() { return 1 << N; }
power_of_two<3>(); // 8
// Template with default parameter
template<typename T = int>
T zero() { return T(0); }
zero(); // T=int (default)
zero<double>(); // T=double
15.2 Class Templates
template<typename T>
class Stack {
vector<T> data;
public:
void push(const T& val) { data.push_back(val); }
void push(T&& val) { data.push_back(move(val)); }
void pop() { if(![Link]()) data.pop_back(); }
T& top() { return [Link](); }
bool empty() const { return [Link](); }
size_t size() const { return [Link](); }
};
Stack<int> si;
[Link](10); [Link](20);
[Link](); // 20
Stack<string> ss;
[Link]('Hello'); [Link]('World');
// Template with multiple parameters + default
template<typename Key, typename Val = string>
class Dictionary { /* ... */ };
Dictionary<int> d; // Val defaults to string
Dictionary<int, double> d2;
15.3 Template Specialisation
Full specialisation: Provide a completely custom implementation for a specific type. Partial specialisation:
Available only for class templates — specialise for a subset of types (e.g., all pointer types).
// General template
template<typename T>
class Formatter {
public:
string format(T val) { return to_string(val); }
};
// Full specialisation for bool
template<>
class Formatter<bool> {
public:
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 29
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
string format(bool val) { return val ? 'true' : 'false'; }
};
// Full specialisation for const char*
template<>
class Formatter<const char*> {
public:
string format(const char* val) { return '"' + string(val) + '"'; }
};
// Partial specialisation – for all pointer types
template<typename T>
class Formatter<T*> {
public:
string format(T* val) {
return val ? 'ptr:' + to_string(*val) : 'nullptr';
}
};
Formatter<int>().format(42); // '42'
Formatter<bool>().format(true); // 'true'
Formatter<int*>().format(&x); // 'ptr:42'
15.4 Variadic Templates (C++11)
Variadic templates accept a variable number of template arguments. The parameter pack Args... expands to
zero or more types.
// Recursive variadic template
template<typename T> // base case (1 argument)
T sum(T x) { return x; }
template<typename T, typename... Rest>
T sum(T first, Rest... rest) {
return first + sum(rest...); // recursive call
}
sum(1, 2, 3, 4, 5); // 15
// Fold expression (C++17 – much simpler)
template<typename... Args>
auto sum_fold(Args... args) { return (args + ...); }
sum_fold(1, 2, 3, 4, 5); // 15
// print_all using fold expression
template<typename... Args>
void print_all(Args&&... args) {
((cout << args << ' '), ...);
}
print_all(1, 3.14, 'hello', true);
// sizeof... – number of arguments in pack
template<typename... T>
constexpr int count() { return sizeof...(T); }
count<int, double, char>(); // 3
15.5 SFINAE (Substitution Failure Is Not An Error)
SFINAE is a rule in C++ template instantiation: if a type substitution into a template would cause a compile error,
the compiler simply discards that overload instead of generating an error. This enables compile-time conditional
enabling of functions based on type properties.
#include <type_traits>
// enable_if – enable function only for integral types
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 30
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
template<typename T>
enable_if_t<is_integral_v<T>, T>
double_it(T x) { return x * 2; }
double_it(5); // OK – int is integral
// double_it(3.14); // SFINAE: double is not integral, overload discarded
// Useful type traits
is_integral_v<int>; // true
is_floating_point_v<float>; // true
is_same_v<int, long>; // false
is_pointer_v<int*>; // true
is_base_of_v<Base, Derived>; // true
is_constructible_v<MyClass, int, string>; // can construct with those args?
// decltype – deduce type of expression at compile time
template<typename T, typename U>
auto add(T a, U b) -> decltype(a+b) { return a+b; }
15.6 Concepts (C++20) – Readable SFINAE
Concepts allow expressing template requirements in a readable, human-friendly way, replacing verbose
SFINAE with clear constraint expressions. Better error messages and improved code documentation.
#include <concepts>
// Define a concept
template<typename T>
concept Numeric = is_integral_v<T> || is_floating_point_v<T>;
template<typename T>
concept Printable = requires(T x) {
{ cout << x } -> same_as<ostream&>;
};
template<typename T>
concept Sortable = requires(T a, T b) {
{ a < b } -> convertible_to<bool>;
};
// Use concept in template
template<Numeric T>
T square(T x) { return x * x; }
square(4); // OK: int satisfies Numeric
square(2.5); // OK: double satisfies Numeric
// square('a'); // ERROR: char does not satisfy Numeric
// Abbreviated template syntax (C++20)
Numeric auto add(Numeric auto a, Numeric auto b) { return a+b; }
15.7 Template Metaprogramming (Intro)
Templates can compute values at compile time, reducing runtime overhead. This is called Template
Metaprogramming (TMP).
// Compile-time factorial
template<int N>
struct Factorial { static const int value = N * Factorial<N-1>::value; };
template<> // specialisation for base case
struct Factorial<0> { static const int value = 1; };
Factorial<5>::value; // 120 – computed at COMPILE TIME
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 31
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
// C++14: constexpr functions are cleaner
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
constexpr int f5 = factorial(5); // 120 at compile time
16. Quick Revision & Exam Tips
Unit IV – Key Exam Points
■ STL = Containers + Algorithms + Iterators + Functors (4 components)
■ Sequence containers: array (fixed, stack), vector (dynamic, contiguous), deque (fast front+back), list
(doubly linked, O(1) mid-insert), forward_list (singly linked)
■ Container adaptors: stack (LIFO), queue (FIFO), priority_queue (heap, O(log n))
■ Associative: set/multiset/map/multimap – Red-Black Tree – O(log n) – sorted
■ Unordered: hash table – average O(1) – NOT sorted – bucket_count, load_factor
■ multiset: duplicates OK; multimap: duplicate keys OK; no operator[] in multimap
■ map['key'] creates entry if not found! Use find() or count() for safe lookup
■ Erase-remove idiom: [Link](remove_if(...), [Link]())
■ Binary search only on sorted ranges; lower_bound/upper_bound return iterators
■ File modes: in, out, app, ate, trunc, binary; seekg/seekp for random access
■ Binary I/O: write(reinterpret_cast(&obj;), sizeof(obj))
Unit V – Key Exam Points
■ Exception handling: try-throw-catch; catch in order most specific to least specific
■ noexcept: compiler optimisation hint; violation calls terminate() (no unwinding)
■ Stack unwinding: destructors called automatically when exception propagates
■ Custom exception: inherit from std::exception, override what() with noexcept
■ thread: join() waits; detach() daemon; must do one before destruction
■ mutex + lock_guard = RAII safe critical section; unique_lock = flexible
■ atomic: lock-free thread safety for simple types; fetch_add, store, load, CAS
■ condition_variable: wait(lock, predicate) avoids spurious wakeups
■ Lambda: [capture](params)->ret{body}; mutable lets you modify value-captured vars
■ unique_ptr: exclusive, move-only; shared_ptr: ref-counted; weak_ptr: non-owning, breaks cycles
■ lvalue: has name; rvalue: temporary; std::move casts to rvalue (O(1) resource steal)
■ std::forward: preserves value category in templates (use in universal references T&&)
■ Templates: function/class; specialisation (full & partial); variadic (Args...)
■ SFINAE: failed substitution = discarded overload; enable_if_t for conditional templates
■ Concepts (C++20): readable template constraints; better error messages
Complexity Reference
Operation vector list deque set/map unordered_*
Random access O(1) O(n) O(1) O(log n) O(1)
Front insert O(n) O(1) O(1) O(log n) O(1)
Back insert O(1)* O(1) O(1) O(log n) O(1)
Middle insert O(n) O(1)^ O(n) O(log n) O(1)
Search (unsorted) O(n) O(n) O(n) O(log n) O(1)
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 32
C++ Programming – Unit IV & V | Complete Student Notes B.E./[Link] CSE
Operation vector list deque set/map unordered_*
Binary search O(log n) N/A O(log n) O(log n) N/A
Delete O(n) O(1)^ O(n) O(log n) O(1)
■ Note: * amortised ^ given iterator to position
Common Mistakes to Avoid in Exams
■ ■ Using map['key'] for lookup – always creates entry with default value if key absent
■ ■ Forgetting erase() after remove()/remove_if() – container size doesn't change otherwise
■ ■ Not joining or detaching a thread before it's destroyed – undefined behaviour / crash
■ ■ Using shared_ptr for circular references without weak_ptr – memory leak
■ ■ Accessing unique_ptr after move() – it's null after move
■ ■ Using binary_search on unsorted range – undefined behaviour
■ ■ Holding raw iterator across push_back if capacity was not reserved – dangling iterator
■ ■ Confusing std::move (cast) with actual moving – std::move just casts, the move constructor does the
work
■ ■ Writing 'catch(exception e)' instead of 'catch(const exception& e)' – slicing!
■ ■ Not making move constructor noexcept – vector won't use it during reallocation
■ End of Notes ■
STL | File Handling | Exceptions | Concurrency | Lambda | Smart Pointers | Templates Page 33