0% found this document useful (0 votes)
5 views33 pages

CPP Unit4 Unit5 Student Notes Expanded

The document provides comprehensive notes on C++ programming, specifically focusing on Unit IV (Standard Template Libraries) and Unit V (Lambda & Concurrency). It covers key concepts such as STL components, various container types, algorithms, exception handling, and concurrency mechanisms. The notes are designed for B.E./B.Tech Computer Science & Engineering students, featuring in-depth explanations and practical examples.

Uploaded by

harunmatthewb
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views33 pages

CPP Unit4 Unit5 Student Notes Expanded

The document provides comprehensive notes on C++ programming, specifically focusing on Unit IV (Standard Template Libraries) and Unit V (Lambda & Concurrency). It covers key concepts such as STL components, various container types, algorithms, exception handling, and concurrency mechanisms. The notes are designed for B.E./B.Tech Computer Science & Engineering students, featuring in-depth explanations and practical examples.

Uploaded by

harunmatthewb
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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

&lt;vector&gt; std::vector

&lt;list&gt; std::list (doubly linked)

&lt;forward_list&gt; std::forward_list (singly linked)

&lt;deque&gt; std::deque

&lt;array&gt; std::array (fixed-size)

&lt;stack&gt;, &lt;queue&gt; stack, queue, priority_queue

&lt;set&gt; set, multiset

&lt;map&gt; 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

&lt;unordered_set&gt; unordered_set, unordered_multiset

&lt;unordered_map&gt; unordered_map, unordered_multimap

&lt;algorithm&gt; sort, find, copy, transform, etc.

&lt;iterator&gt; Iterator adapters & utilities

&lt;functional&gt; Functors: greater<>, less<>, bind, function

&lt;numeric&gt; 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

[&amp;] 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

[&amp;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

You might also like