Book4_Modern_CPP_STL
Book4_Modern_CPP_STL
January 2025
Contents
Contents 2
1 Containers 19
1.1 Sequence Containers (std::vector, std::list, std::deque) . . . . . 19
1.1.1 std::vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.1.2 std::list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.1.3 std::deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.1.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.2 Associative Containers (std::map, std::set, std::unordered map) . 29
1.2.1 std::map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.2.2 std::set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.2.3 std::unordered map . . . . . . . . . . . . . . . . . . . . . . . . 34
1.2.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.3 Container Adapters (std::stack, std::queue, std::priority queue) 39
1.3.1 std::stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.3.2 std::queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
1.3.3 std::priority queue . . . . . . . . . . . . . . . . . . . . . . . 44
1.3.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2
3
2 Algorithms 48
2.1 Sorting, Searching, and Modifying Algorithms . . . . . . . . . . . . . . . . . 48
2.1.1 Sorting Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.1.2 Searching Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.1.3 Modifying Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.1.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.2 Parallel Algorithms (C++17) . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.2.1 Overview of Parallel Algorithms . . . . . . . . . . . . . . . . . . . . . 61
2.2.2 Execution Policies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.2.3 Common Parallel Algorithms . . . . . . . . . . . . . . . . . . . . . . 63
2.2.4 Thread Safety and Considerations . . . . . . . . . . . . . . . . . . . . 67
2.2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3 Utilities 70
3.1 Smart Pointers (std::unique ptr, std::shared ptr, std::weak ptr) 70
3.1.1 std::unique ptr . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.1.2 std::shared ptr . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.1.3 std::weak ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.1.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.2 std::optional, std::variant, std::any . . . . . . . . . . . . . . . 79
3.2.1 std::optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.2.2 std::variant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.2.3 std::any . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
3.2.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.3 std::function and std::bind . . . . . . . . . . . . . . . . . . . . . . 88
3.3.1 std::function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.3.2 std::bind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.3.3 Combining std::function and std::bind . . . . . . . . . . . . 93
4
3.3.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Appendices 144
Appendix A: Overview of STL Containers . . . . . . . . . . . . . . . . . . . . . . . 144
Appendix B: STL Algorithms Cheat Sheet . . . . . . . . . . . . . . . . . . . . . . . 145
Appendix C: Iterators and Range . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Appendix D: Smart Pointers and Memory Management . . . . . . . . . . . . . . . . 146
6
References 151
Modern C++ Handbooks
• Content:
– Introduction to C++:
7
8
– Control Flow:
– Functions:
– Practical Examples:
• Content:
– C++11 Features:
* Structured bindings.
* if and switch with initializers.
* inline variables.
* Fold expressions.
– C++20 Features:
* Ranges library.
* Coroutines.
* Three-way comparison (<=> operator).
– C++23 Features:
• Content:
• Content:
– Containers:
– Algorithms:
* Iterator categories.
* Ranges library (C++20).
– Practical Examples:
* Custom allocators.
* Performance benchmarks.
• Content:
13
* Lock-free programming.
* Custom memory management.
• Content:
– Code Quality:
– Performance Optimization:
– Design Principles:
– Security:
– Practical Examples:
– Deployment (CI/CD):
• Content:
– Scientific Computing:
* Real-time programming.
* Low-level hardware interaction.
– Practical Examples:
* Domain-specific optimizations.
• Content:
16
• Content:
– Template Metaprogramming:
* Custom allocators.
* Memory pools and arenas.
* Garbage collection techniques.
17
– Performance Tuning:
* Cache optimization.
* SIMD (Single Instruction, Multiple Data) programming.
* Profiling and benchmarking tools.
– Advanced Libraries:
• Content:
– Case Studies:
Containers
1.1.1 std::vector
1. Overview:
19
20
are stored in contiguous memory locations, which makes it highly efficient for random
access and iteration.
2. Key Features:
3. Common Operations:
• Declaration:
• Adding Elements:
21
• Accessing Elements:
• Removing Elements:
4. Use Cases:
• When most insertions and deletions occur at the end of the sequence.
5. Advanced Features:
• Custom Allocators:
1.1.2 std::list
1. Overview:
std::list is a doubly linked list container. Unlike std::vector, it does not store
elements in contiguous memory. Instead, each element in a std::list contains
pointers to the previous and next elements, allowing for efficient insertions and deletions
at any position.
2. Key Features:
• Time Complexity:
3. Common Operations:
• Declaration:
• Adding Elements:
• Accessing Elements:
• Removing Elements:
• Size:
4. Use Cases:
• When frequent insertions and deletions are required at both the beginning and end of
the sequence.
• When you need to insert or delete elements in the middle of the sequence without
invalidating iterators.
5. Advanced Features:
• Splice:
std::list<int> lst2;
lst.splice(lst.begin(), lst2); // Move elements from lst2 to lst
• Merge:
25
• Sort:
1.1.3 std::deque
1. Overview:
std::deque (short for ”double-ended queue”) is a sequence container that allows fast
insertions and deletions at both the beginning and the end. It is implemented as a dynamic
array of fixed-size arrays, providing a balance between the random access efficiency of
std::vector and the flexibility of std::list.
2. Key Features:
• Time Complexity:
3. Common Operations:
• Declaration:
• Adding Elements:
• Accessing Elements:
• Removing Elements:
• Size:
27
4. Use Cases:
• When you need fast insertions and deletions at both the beginning and end of the
sequence.
• When random access to elements is required.
• When you need a more flexible alternative to std::vector for certain operations.
5. Advanced Features:
• Custom Allocators:
Use Case Fast access, end ops Frequent middle Fast front/back ops
ops
1.1.4 Summary
• std::vector: Ideal for scenarios requiring fast random access and memory locality.
Best suited for cases where most operations occur at the end of the sequence.
• std::list: Suitable for frequent insertions and deletions at any position, especially
when iterator invalidation is a concern.
Understanding the strengths and weaknesses of each sequence container will help you choose
the right one for your specific use case, ensuring optimal performance and efficiency in your
C++ programs.
29
1.2.1 std::map
1. Overview:
std::map is an associative container that stores key-value pairs in a sorted order based
on the keys. It is implemented as a balanced binary search tree (typically a Red-Black
Tree), which ensures that elements are always sorted and that operations like insertion,
deletion, and lookup are efficient.
2. Key Features:
• Sorted Order: Elements in a std::map are always sorted by key. By default, the
sorting is done in ascending order, but you can provide a custom comparator to
change the order.
• Time Complexity:
30
– Insertion: O(log n)
– Deletion: O(log n)
– Lookup: O(log n)
3. Common Operations:
• Declaration:
• Inserting Elements:
myMap.insert({1, "Apple"});
myMap[2] = "Banana"; // Insert or update using the subscript
,→ operator
• Accessing Elements:
• Removing Elements:
• Size:
4. Use Cases:
5. Advanced Features:
• Custom Comparators:
struct CaseInsensitiveCompare {
bool operator()(const std::string& a, const std::string& b)
,→ const {
return std::lexicographical_compare(a.begin(), a.end(),
,→ b.begin(), b.end(), [](char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
});
}
};
std::map<std::string, int, CaseInsensitiveCompare> myMap;
32
1.2.2 std::set
1. Overview:
std::set is an associative container that stores unique elements in a sorted order. Like
std::map, it is implemented as a balanced binary search tree, ensuring that elements are
always sorted and that operations like insertion, deletion, and lookup are efficient.
2. Key Features:
• Sorted Order: Elements in a std::set are always sorted. By default, the sorting
is done in ascending order, but you can provide a custom comparator to change the
order.
• Unique Elements: Each element in a std::set must be unique. Attempting to
insert a duplicate element will not change the set.
• Time Complexity:
– Insertion: O(log n)
– Deletion: O(log n)
– Lookup: O(log n)
3. Common Operations:
33
• Declaration:
• Inserting Elements:
mySet.insert(42);
mySet.emplace(42); // Construct element in-place
• Accessing Elements:
• Removing Elements:
• Size:
4. Use Cases:
5. Advanced Features:
• Custom Comparators:
struct CaseInsensitiveCompare {
bool operator()(const std::string& a, const std::string& b)
,→ const {
return std::lexicographical_compare(a.begin(), a.end(),
,→ b.begin(), b.end(), [](char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
});
}
};
std::set<std::string, CaseInsensitiveCompare> mySet;
2. Key Features:
• Time Complexity:
3. Common Operations:
• Declaration:
• Inserting Elements:
myMap.insert({1, "Apple"});
myMap[2] = "Banana"; // Insert or update using the subscript
,→ operator
• Accessing Elements:
36
• Removing Elements:
• Size:
4. Use Cases:
• When you need to store key-value pairs with fast access by key.
• When the order of elements is not important.
• When keys are unique and need to be accessed quickly.
5. Advanced Features:
struct MyHash {
size_t operator()(const std::string& key) const {
size_t hash = 0;
37
1.2.4 Summary
• std::map: Ideal for storing key-value pairs in a sorted order. Best suited for scenarios
where you need efficient lookup, insertion, and deletion by key while maintaining a
specific order.
• std::set: Suitable for storing unique elements in a sorted order. Use it when you need
efficient lookup, insertion, and deletion of elements while maintaining uniqueness and
order.
• std::unordered map: Perfect for scenarios requiring fast access to key-value pairs
without the need for sorting. Use it when the order of elements is not important, and you
need average constant-time complexity for operations.
Understanding the strengths and weaknesses of each associative container will help you choose
the right one for your specific use case, ensuring optimal performance and efficiency in your
C++ programs.
39
1.3.1 std::stack
1. Overview:
2. Key Features:
• LIFO Principle: The last element added to the stack is the first one to be removed.
3. Common Operations:
• Declaration:
• Adding Elements:
• Accessing Elements:
• Removing Elements:
• Checking if Empty:
• Size:
4. Use Cases:
41
• When you require a simple interface for adding and removing elements from one
end.
• When the order of element removal is important (last added is first removed).
5. Advanced Features:
• Swapping Stacks:
1.3.2 std::queue
1. Overview:
2. Key Features:
42
• FIFO Principle: The first element added to the queue is the first one to be removed.
• Restricted Interface: Provides a minimal interface for queue operations: push,
pop, front, back, and empty.
• Underlying Container: By default, uses std::deque as the underlying container,
but you can change it to std::list.
3. Common Operations:
• Declaration:
• Adding Elements:
• Accessing Elements:
• Removing Elements:
• Checking if Empty:
43
• Size:
4. Use Cases:
• When you require a simple interface for adding elements to one end and removing
them from the other.
• When the order of element removal is important (first added is first removed).
5. Advanced Features:
• Swapping Queues:
2. Key Features:
• Priority-Based Order: Elements are removed based on their priority, not the order
in which they were added.
3. Common Operations:
• Declaration:
• Adding Elements:
• Accessing Elements:
• Removing Elements:
• Checking if Empty:
• Size:
4. Use Cases:
5. Advanced Features:
• Custom Comparator:
struct MyComparator {
bool operator()(int a, int b) const {
return a > b; // Custom priority logic
}
};
std::priority_queue<int, std::vector<int>, MyComparator>
,→ myPriorityQueue;
1.3.4 Summary
• std::stack: Ideal for implementing a Last-In-First-Out (LIFO) data structure. Use it
when you need to add and remove elements from one end only.
Understanding the strengths and weaknesses of each container adapter will help you choose the
right one for your specific use case, ensuring optimal performance and efficiency in your C++
programs.
Chapter 2
Algorithms
48
49
• std::sort: Sorts elements in a range into ascending order (or another order
specified by a comparator).
• std::partial sort: Partially sorts a range, ensuring that the first n elements
are the smallest (or largest) in the range.
• std::nth element: Rearranges elements such that the n-th element is in its
correct sorted position, and all elements before it are less than or equal to it.
2. Common Operations:
• std::sort:
• std::stable sort:
• std::partial sort:
50
• std::nth element:
• std::is sorted:
3. Use Cases:
• std::stable sort: When you need to sort elements while preserving the
relative order of equivalent elements.
• std::partial sort: When you only need the top n elements to be sorted.
• std::nth element: When you need to find the n-th smallest or largest element
in a range.
• std::is sorted until: When you need to find the first unsorted element in a
range.
• std::find if: Finds the first element in a range that satisfies a predicate.
• std::find if not: Finds the first element in a range that does not satisfy a
predicate.
• std::lower bound: Finds the first element in a sorted range that is not less than
a given value.
• std::upper bound: Finds the first element in a sorted range that is greater than
a given value.
52
• std::equal range: Finds a subrange of elements that are equal to a given value
in a sorted range.
• std::search: Finds the first occurrence of a subsequence within a range.
• std::find end: Finds the last occurrence of a subsequence within a range.
2. Common Operations:
• std::find:
• std::find if:
• std::find if not:
• std::binary search:
• std::lower bound:
• std::upper bound:
• std::equal range:
• std::search:
• std::find end:
3. Use Cases:
• std::find: When you need to find the first occurrence of a specific value in a
range.
• std::find if: When you need to find the first element that satisfies a custom
condition.
• std::find if not: When you need to find the first element that does not satisfy
a custom condition.
55
2. Common Operations:
• std::copy:
• std::move:
• std::transform:
57
• std::replace:
• std::replace if:
• std::fill:
std::vector<int> vec(5);
std::fill(vec.begin(), vec.end(), 42); // Fills the vector with
,→ 42
• std::fill n:
std::vector<int> vec(5);
std::fill_n(vec.begin(), 3, 42); // Fills the first 3 elements
,→ with 42
• std::remove:
58
• std::remove if:
• std::unique:
• std::reverse:
• std::rotate:
• std::shuffle:
3. Use Cases:
• std::copy: When you need to copy elements from one range to another.
• std::move: When you need to move elements from one range to another.
• std::transform: When you need to apply a function to each element in a range
and store the result.
• std::replace: When you need to replace all occurrences of a value in a range
with another value.
• std::replace if: When you need to replace elements that satisfy a predicate
with another value.
• std::fill: When you need to fill a range with a specific value.
• std::fill n: When you need to fill the first n elements of a range with a specific
value.
• std::remove: When you need to remove elements equal to a specific value from
a range.
• std::remove if: When you need to remove elements that satisfy a predicate
from a range.
• std::unique: When you need to remove consecutive duplicate elements from a
range.
60
2.1.4 Summary
• Sorting Algorithms: Use std::sort for general sorting, std::stable sort for
stable sorting, std::partial sort for partial sorting, and std::nth element
for finding the n-th element.
• Modifying Algorithms: Use std::copy and std::move for copying and moving
elements, std::transform for applying functions, std::replace for replacing
values, std::fill for filling ranges, std::remove for removing values, and
std::unique for removing consecutive duplicates.
Understanding the strengths and weaknesses of each algorithm will help you choose the right
one for your specific use case, ensuring optimal performance and efficiency in your C++
programs.
61
Key Features:
• Parallel Execution: Algorithms can run in parallel, utilizing multiple threads to improve
performance.
• Vectorized Execution: Algorithms can use SIMD (Single Instruction, Multiple Data)
instructions for further optimization.
62
• Thread Safety: Parallel algorithms are designed to be thread-safe, but you must ensure
that the operations performed by the algorithm are also thread-safe.
Execution Policies:
• std::execution::seq:
• std::execution::par:
63
– Example:
• std::execution::par unseq:
– The algorithm runs in parallel and may use vectorized instructions (SIMD).
– Suitable for algorithms that can benefit from both parallel execution and
vectorization.
– Example:
• std::sort:
• std::stable sort:
2. Searching Algorithms
• std::find:
• std::find if:
– Example:
3. Modifying Algorithms
• std::transform:
– Applies a function to each element in a range and stores the result in another
range in parallel.
– Example:
• std::for each:
– Applies a function to each element in a range in parallel.
– Example:
• std::replace:
– Replaces all occurrences of a value in a range with another value in parallel.
– Example:
• std::fill:
– Fills a range with a specific value in parallel.
– Example:
std::vector<int> vec(5);
std::fill(std::execution::par, vec.begin(), vec.end(), 42); //
,→ Parallel fill
• std::reduce:
– Reduces a range of elements to a single value using a binary operation in
parallel.
– Example:
• std::accumulate:
67
• Avoid Data Races: Ensure that the operations performed by the algorithm do not modify
shared data concurrently without proper synchronization.
• Use Thread-Safe Functions: If the algorithm uses custom functions or lambdas, ensure
that these functions are thread-safe.
#include <iostream>
#include <vector>
#include <algorithm>
68
#include <execution>
int main() {
std::vector<int> vec = {5, 3, 1, 4, 2};
// Parallel sort
std::sort(std::execution::par, vec.begin(), vec.end());
// Parallel transform
std::vector<int> result(vec.size());
std::transform(std::execution::par, vec.begin(), vec.end(),
,→ result.begin(), [](int x) {
return x * 2;
});
// Print results
for (int x : result) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
2.2.5 Summary
• Parallel Algorithms: C++17 introduced parallel execution policies
(std::execution::par, std::execution::par unseq) to enable parallel
and vectorized execution of standard algorithms.
• Thread Safety: Ensure that operations performed by parallel algorithms are thread-safe to
avoid data races.
Understanding and using parallel algorithms effectively can significantly improve the
performance of computationally intensive tasks in your C++ programs.
Chapter 3
Utilities
std::unique ptr is a smart pointer that owns and manages a dynamically allocated
object. It ensures that the object is automatically deleted when the std::unique ptr
70
71
goes out of scope. The key feature of std::unique ptr is that it enforces exclusive
ownership, meaning that only one std::unique ptr can own a particular resource at
any given time.
2. Key Features:
3. Common Operations:
• Declaration:
• Releasing Ownership:
72
• Custom Deleter:
4. Use Cases:
5. Example:
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> ptr(new int(42));
73
2. Key Features:
• Shared Ownership: Multiple std::shared ptr instances can own the same
resource.
• Automatic Cleanup: The managed object is automatically deleted when the last
std::shared ptr owning it is destroyed or reset.
• Reference Counting: Keeps track of the number of std::shared ptr instances
that own the resource.
• Custom Deleters: You can specify a custom deleter to handle resource cleanup in a
specific way.
74
3. Common Operations:
• Declaration:
• Custom Deleter:
4. Use Cases:
• When you need to manage resources that are shared across multiple parts of your
code.
5. Example:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr(new int(42));
std::cout << *ptr << std::endl; // Output: 42
2. Key Features:
• Non-Owning Reference: Does not own the resource and does not affect the
reference count.
• Breaking Circular References: Helps to break circular references that can occur
with std::shared ptr.
• Accessing the Managed Object: You can create a std::shared ptr from a
std::weak ptr to access the managed object, but you must check if the object
still exists.
3. Common Operations:
• Declaration:
• Checking Expiration:
if (weakPtr.expired()) {
std::cout << "Object has been deleted" << std::endl;
}
4. Use Cases:
• When you need to break circular references that can occur with
std::shared ptr.
• When you want to check if an object still exists without affecting its lifetime.
5. Example:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr(new int(42));
std::weak_ptr<int> weakPtr(sharedPtr);
if (weakPtr.expired()) {
std::cout << "Object has been deleted" << std::endl; //
,→ Output: Object has been deleted
}
return 0;
}
3.1.4 Summary
• std::unique ptr: Ideal for exclusive ownership of a resource. Use it when you need
automatic cleanup and want to enforce single ownership.
• std::shared ptr: Suitable for shared ownership of a resource. Use it when multiple
parts of your code need to share access to the same resource.
• std::weak ptr: Useful for breaking circular references and providing non-owning
references. Use it when you need to check if an object still exists without affecting its
lifetime.
Understanding the strengths and weaknesses of each smart pointer will help you choose the right
one for your specific use case, ensuring optimal memory management and avoiding common
pitfalls in your C++ programs.
79
3.2.1 std::optional
1. Overview:
std::optional is a utility that represents an optional value, i.e., a value that may or
may not be present. It is particularly useful for functions that may or may not return a
value, or for data members that may or may not be initialized.
2. Key Features:
• Type Safety: Ensures that the value, if present, is of the specified type.
3. Common Operations:
• Declaration:
80
if (opt.has_value()) {
std::cout << "Value: " << opt.value() << std::endl;
} else {
std::cout << "No value" << std::endl;
}
4. Use Cases:
• When you need to represent a value that can be absent without using pointers or
special sentinel values.
5. Example:
#include <iostream>
#include <optional>
int main() {
auto result = divide(10, 2);
if (result) {
std::cout << "Result: " << *result << std::endl; // Output:
,→ Result: 5
} else {
std::cout << "Division by zero" << std::endl;
}
return 0;
}
3.2.2 std::variant
1. Overview:
82
std::variant is a type-safe union that can hold a value of one of several specified
types. It is useful when you need to store a value that can be one of several types, and you
want to ensure type safety.
2. Key Features:
• Index-Based Access: Allows accessing the type of the currently stored value using
index().
3. Common Operations:
• Declaration:
• Assigning a Value:
if (std::holds_alternative<int>(var)) {
int value = std::get<int>(var); // Get the int value
} else if (std::holds_alternative<double>(var)) {
double value = std::get<double>(var); // Get the double value
} else if (std::holds_alternative<std::string>(var)) {
std::string value = std::get<std::string>(var); // Get the
,→ std::string value
}
• Visitation:
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, var);
• Index-Based Access:
4. Use Cases:
• When you need to store a value that can be one of several types.
• When you want to ensure type safety in a union-like structure.
• When you need to perform operations on a value whose type is not known at compile
time.
5. Example:
84
#include <iostream>
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> var = "Hello";
std::visit([](auto&& arg) {
std::cout << arg << std::endl; // Output: Hello
}, var);
var = 3.14;
std::cout << "Index: " << var.index() << std::endl; // Output:
,→ Index: 1
return 0;
}
3.2.3 std::any
1. Overview:
std::any is a type-erased container that can hold a value of any type. It is useful when
you need to store a value whose type is not known at compile time, and you want to defer
type checking to runtime.
2. Key Features:
• Type Checking: Allows checking the type of the stored value at runtime.
3. Common Operations:
• Declaration:
• Assigning a Value:
if (anyValue.type() == typeid(int)) {
int value = std::any_cast<int>(anyValue); // Get the int
,→ value
} else if (anyValue.type() == typeid(double)) {
double value = std::any_cast<double>(anyValue); // Get the
,→ double value
} else if (anyValue.type() == typeid(std::string)) {
std::string value = std::any_cast<std::string>(anyValue); //
,→ Get the std::string value
}
4. Use Cases:
• When you need to store a value whose type is not known at compile time.
• When you need a flexible container that can hold any type of value.
5. Example:
#include <iostream>
#include <any>
#include <string>
int main() {
std::any anyValue = 42;
if (anyValue.type() == typeid(int)) {
int value = std::any_cast<int>(anyValue);
std::cout << "Value: " << value << std::endl; // Output:
,→ Value: 42
}
anyValue = std::string("Hello");
if (anyValue.type() == typeid(std::string)) {
std::string value = std::any_cast<std::string>(anyValue);
std::cout << "Value: " << value << std::endl; // Output:
,→ Value: Hello
}
87
return 0;
}
3.2.4 Summary
• std::optional: Use it when you need to represent an optional value that may or may
not be present.
• std::variant: Use it when you need to store a value that can be one of several types,
ensuring type safety.
• std::any: Use it when you need to store a value whose type is not known at compile
time, deferring type checking to runtime.
Understanding the strengths and weaknesses of each utility will help you choose the right one
for your specific use case, ensuring type safety and flexibility in your C++ programs.
88
3.3.1 std::function
1. Overview:
std::function is a polymorphic function wrapper that can store, copy, and invoke
any callable object—such as functions, lambdas, and function objects—that matches a
specific signature. It is part of the <functional> header and is particularly useful for
implementing callbacks, event handlers, and other scenarios where you need to store and
invoke callable objects.
2. Key Features:
• Polymorphic: Can store any callable object that matches the specified signature.
• Type Safety: Ensures that the stored callable object matches the expected signature.
• Flexibility: Can be used to store functions, lambdas, function objects, and more.
• Copyable and Movable: Supports copy and move semantics, making it easy to pass
around.
3. Common Operations:
• Declaration:
89
• Assigning a Callable:
if (func) {
std::cout << "Function is callable" << std::endl;
} else {
std::cout << "Function is empty" << std::endl;
}
4. Use Cases:
5. Example:
#include <iostream>
#include <functional>
int main() {
std::function<int(int, int)> func = add; // Stores a function
,→ pointer
std::cout << "Result: " << func(2, 3) << std::endl; // Output:
,→ Result: 5
return 0;
}
3.3.2 std::bind
1. Overview:
std::bind is a utility that allows you to create a new callable object by binding
arguments to a function or callable object. It is part of the <functional> header and
is particularly useful for creating function objects with pre-bound arguments, which can
then be passed around and invoked later.
2. Key Features:
91
3. Common Operations:
• Binding Arguments:
• Using Placeholders:
struct MyClass {
int multiply(int a, int b) { return a * b; }
};
MyClass obj;
auto boundFunc = std::bind(&MyClass::multiply, &obj,
,→ std::placeholders::_1, std::placeholders::_2);
int result = boundFunc(2, 3); // Calls obj.multiply(2, 3), result
,→ = 6
92
4. Use Cases:
5. Example:
#include <iostream>
#include <functional>
struct MyClass {
int multiply(int a, int b) { return a * b; }
};
int main() {
// Binding arguments to a function
auto boundFunc = std::bind(add, 2, 3);
std::cout << "Result: " << boundFunc() << std::endl; // Output:
,→ Result: 5
// Using placeholders
auto boundFunc2 = std::bind(add, std::placeholders::_1, 3);
std::cout << "Result: " << boundFunc2(2) << std::endl; // Output:
,→ Result: 5
93
return 0;
}
Example:
#include <iostream>
#include <functional>
int main() {
// Bind the first argument to 2
auto boundFunc = std::bind(add, 2, std::placeholders::_1);
return 0;
}
3.3.4 Summary
• std::function: Use it when you need to store, pass, or invoke callable objects in a
type-safe and flexible manner.
• std::bind: Use it when you need to create callable objects with pre-bound arguments
or when you need to bind member functions to specific objects.
Understanding the strengths and weaknesses of std::function and std::bind will help
you choose the right tool for your specific use case, ensuring flexibility and type safety in your
C++ programs.
Chapter 4
1. Input Iterators
95
96
2. Output Iterators
3. Forward Iterators
4. Bidirectional Iterators
Each category has specific requirements and guarantees, which determine how iterators can be
used in algorithms and operations.
Input iterators are the simplest type of iterator, providing read-only access to elements in a
sequence. They are typically used for single-pass algorithms, where each element is
processed exactly once.
2. Key Features:
• Single-Pass: Can only traverse the sequence once; cannot go back to previous
elements.
3. Common Operations:
• Dereference:
97
• Increment:
• Equality Comparison:
4. Use Cases:
• When working with algorithms that only require forward traversal, such as
std::find or std::accumulate.
5. Example:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin(); // Input iterator
return 0;
}
Output iterators provide write-only access to elements in a sequence. They are typically
used for single-pass algorithms, where each element is written exactly once.
2. Key Features:
• Single-Pass: Can only traverse the sequence once; cannot go back to previous
elements.
3. Common Operations:
• Dereference:
• Increment:
4. Use Cases:
• When working with algorithms that only require forward traversal, such as
std::copy or std::fill.
5. Example:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec(5);
auto it = vec.begin(); // Output iterator
return 0;
}
Forward iterators provide read and write access to elements in a sequence and support
multi-pass traversal. They are more powerful than input and output iterators, allowing
algorithms to traverse the sequence multiple times.
2. Key Features:
• Read and Write Access: Can read and write elements in the sequence.
3. Common Operations:
• Dereference:
• Increment:
• Equality Comparison:
4. Use Cases:
• When you need to read and write elements in a sequence with multi-pass traversal.
101
5. Example:
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> list = {1, 2, 3, 4, 5};
auto it = list.begin(); // Forward iterator
return 0;
}
Bidirectional iterators provide all the capabilities of forward iterators, with the additional
ability to traverse the sequence in both forward and backward directions.
2. Key Features:
102
• Read and Write Access: Can read and write elements in the sequence.
3. Common Operations:
• Dereference:
• Increment:
• Decrement:
• Equality Comparison:
4. Use Cases:
• When you need to traverse a sequence in both forward and backward directions.
103
5. Example:
#include <iostream>
#include <list>
int main() {
std::list<int> list = {1, 2, 3, 4, 5};
auto it = list.end(); // Bidirectional iterator
return 0;
}
Random access iterators provide the most functionality, allowing direct access to any
element in the sequence. They support all the capabilities of bidirectional iterators, with
the addition of random access and arithmetic operations.
2. Key Features:
• Read and Write Access: Can read and write elements in the sequence.
104
3. Common Operations:
• Dereference:
• Increment:
• Decrement:
• Arithmetic Operations:
4. Use Cases:
5. Example:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin(); // Random access iterator
return 0;
}
106
4.1.7 Summary
• Input Iterators: Provide read-only access and support single-pass traversal.
• Forward Iterators: Provide read and write access and support multi-pass traversal.
• Bidirectional Iterators: Provide all the capabilities of forward iterators, with the addition
of backward traversal.
• Random Access Iterators: Provide all the capabilities of bidirectional iterators, with the
addition of random access and arithmetic operations.
Understanding the strengths and weaknesses of each iterator category will help you choose the
right tool for your specific use case, ensuring efficient and correct traversal and manipulation of
container elements in your C++ programs.
107
• Range Concepts: Define what constitutes a range and provide constraints for working
with ranges.
• Range Adaptors: Allow you to transform, filter, and compose ranges in a declarative
manner.
• Range Algorithms: Provide a set of algorithms that operate directly on ranges, rather
than iterators.
The Ranges library is part of the <ranges> header and is designed to work seamlessly with
existing STL containers and algorithms.
• std::ranges::range: A type that can be iterated over using begin() and end().
Example:
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
return 0;
}
109
Example:
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
return 0;
}
Example:
#include <iostream>
#include <ranges>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {5, 3, 1, 4, 2};
return 0;
}
Example:
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Create a view that filters even numbers, squares them, and takes
,→ the first 3 elements
auto view = vec | std::views::filter([](int x) { return x % 2 == 0; })
112
| std::views::transform([](int x) { return x * x; })
| std::views::take(3);
return 0;
}
• Expressiveness: Range adaptors and algorithms allow you to express complex operations
in a clear and concise manner.
• Readability: Code using the Ranges library is often more readable and easier to
understand.
• Safety: Range concepts and constraints help catch errors at compile time, improving code
safety.
4.2.7 Summary
• Range Concepts: Define what constitutes a range and provide constraints for working
with ranges.
113
• Range Adaptors: Allow you to transform, filter, and compose ranges in a declarative
manner.
• Range Algorithms: Provide a set of algorithms that operate directly on ranges, making
code more expressive and easier to read.
Understanding and using the Ranges library effectively can significantly improve the readability,
maintainability, and safety of your C++ programs. By leveraging range adaptors and algorithms,
you can write more expressive and composable code that is easier to understand and maintain.
Chapter 5
Practical Examples
114
115
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort
int main() {
std::vector<int> numbers = {5, 2, 9, 1, 5, 6};
return 0;
}
Explanation:
• std::sort: Sorts the elements in the range [begin, end) in ascending order by
default.
• Range-based for loop: Used to iterate over the sorted vector and print its elements.
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort
#include <string>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie", "David",
,→ "Eve"};
return 0;
}
Explanation:
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort
int main() {
std::vector<int> numbers = {5, 2, 9, 1, 5, 6};
return 0;
}
Explanation:
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort and std::binary_search
118
int main() {
std::vector<int> numbers = {5, 2, 9, 1, 5, 6};
return 0;
}
Explanation:
• std::binary search: Checks if the target element exists in the sorted range
[begin, end).
• Efficiency: Binary search works in O(log n) time, making it suitable for large datasets.
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort, std::lower_bound, and
,→ std::upper_bound
int main() {
std::vector<int> numbers = {1, 2, 4, 4, 5, 6};
return 0;
}
Explanation:
• std::lower bound: Returns an iterator to the first element that is not less than the
target value.
• std::upper bound: Returns an iterator to the first element that is greater than the
target value.
120
#include <iostream>
#include <vector>
#include <algorithm> // for std::count
int main() {
std::vector<int> numbers = {5, 2, 9, 1, 5, 6};
std::cout << target << " appears " << count << " times." << std::endl;
,→ // Output: 5 appears 2 times.
return 0;
}
Explanation:
• std::count: Counts the number of elements in the range [begin, end) that are
equal to the target value.
• Linear Search: This algorithm works in O(n) time, where n is the size of the container.
121
std::map is an associative container that stores key-value pairs in sorted order. It is useful for
scenarios where you need to look up values by their keys efficiently.
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> ageMap;
return 0;
}
122
Explanation:
• Efficiency: Lookup, insertion, and deletion operations are performed in O(log n) time.
#include <iostream>
#include <set>
int main() {
std::set<int> uniqueNumbers;
// Insert elements
uniqueNumbers.insert(5);
uniqueNumbers.insert(2);
uniqueNumbers.insert(9);
uniqueNumbers.insert(2); // Duplicate, will not be inserted
return 0;
}
123
Explanation:
• std::set: Ensures that all elements are unique and stored in sorted order.
• Efficiency: Insertion, deletion, and lookup operations are performed in O(log n) time.
#include <iostream>
#include <vector>
#include <algorithm> // for std::sort and std::set_intersection
int main() {
std::vector<int> vec1 = {1, 3, 5, 7, 9};
std::vector<int> vec2 = {2, 3, 5, 8, 9};
return 0;
}
Explanation:
#include <iostream>
#include <vector>
#include <numeric> // for std::accumulate
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::cout << "Sum: " << sum << std::endl; // Output: Sum: 15
125
return 0;
}
Explanation:
• Flexibility: Can also be used with custom operations (e.g., multiplication) by providing a
binary function.
5.1.11 Summary
• STL Containers: Use std::vector for dynamic arrays, std::map for key-value
pairs, and std::set for unique elements.
• STL Algorithms: Use std::sort for sorting, std::binary search for searching,
std::count for counting, and std::accumulate for summation.
These examples demonstrate how to effectively use STL containers and algorithms to write
efficient and maintainable C++ programs. By mastering these tools, you can tackle a wide range
of programming challenges with ease.
Chapter 6
126
127
• Performance Optimization: Custom allocators can be tailored to specific use cases, such
as memory pools or arena allocators, to reduce fragmentation and improve performance.
• Integration with Existing Systems: Custom allocators can integrate with existing
memory management systems, such as those used in game engines or real-time systems.
Additionally, custom allocators must provide typedefs for value type, pointer,
const pointer, size type, and difference type.
128
#include <iostream>
#include <memory>
#include <vector>
T* allocate(std::size_t n) {
if (n > PoolSize || next + n * sizeof(T) > pool.get() + PoolSize *
,→ sizeof(T)) {
throw std::bad_alloc();
}
T* result = reinterpret_cast<T*>(next);
next += n * sizeof(T);
return result;
}
129
private:
std::unique_ptr<char[]> pool;
char* next;
};
int main() {
// Use the custom allocator with a vector
std::vector<int, PoolAllocator<int, 10>> vec;
return 0;
}
Explanation:
• allocate: Allocates memory from the pool. Throws std::bad alloc if the pool is
exhausted.
• deallocate: A no-op in this simple allocator, as memory is not freed until the
allocator is destroyed.
• destroy: Destroys the object at the specified memory location by calling its destructor.
#include <iostream>
#include <vector>
#include <memory>
131
T* allocate(std::size_t n) {
if (n > PoolSize || next + n * sizeof(T) > pool.get() + PoolSize *
,→ sizeof(T)) {
throw std::bad_alloc();
}
T* result = reinterpret_cast<T*>(next);
next += n * sizeof(T);
return result;
}
void destroy(U* p) {
p->˜U();
}
private:
std::unique_ptr<char[]> pool;
char* next;
};
int main() {
// Use the custom allocator with a vector
std::vector<int, PoolAllocator<int, 10>> vec;
return 0;
}
Explanation:
• Memory Management: The vector allocates memory from the fixed-size pool managed
133
2. Arena Allocators
Arena allocators allocate memory from a fixed-size arena and deallocate all memory at
once. This is useful for scenarios where memory is allocated and deallocated in phases,
such as in game engines.
3. Shared Memory
Custom allocators can be used to allocate memory from shared memory regions, allowing
multiple processes to share data.
4. GPU Memory
Custom allocators can manage memory allocations on the GPU, enabling efficient data
transfer between the CPU and GPU.
6.1.6 Summary
• Custom Allocators: Provide a way to control memory allocation and deallocation for
STL containers.
• Implementation: Custom allocators must conform to the allocator interface and provide
methods for allocation, deallocation, construction, and destruction.
• Use Cases: Custom allocators are useful for optimizing performance, managing memory
pools, and integrating with specialized memory systems.
Understanding and using custom allocators effectively can help you optimize memory
management in your C++ programs, leading to improved performance and resource utilization.
135
• Identify Bottlenecks: Benchmarks help pinpoint parts of the code that are slow or
inefficient.
Key Considerations:
136
• Define Objectives: Clearly define what you want to measure (e.g., execution time,
memory usage, throughput).
• Choose Metrics: Select appropriate metrics, such as time complexity, space complexity,
or cache performance.
• Control Variables: Ensure that benchmarks are run under consistent conditions (e.g.,
same hardware, same input data).
• Use Realistic Data: Use realistic input data that reflects the actual use case.
#include <iostream>
#include <vector>
#include <list>
#include <chrono>
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 1000);
return numbers;
}
// Insert elements
for (int num : numbers) {
vec.push_back(num);
}
// Remove elements
while (!vec.empty()) {
vec.pop_back();
}
138
// Insert elements
for (int num : numbers) {
lst.push_back(num);
}
// Remove elements
while (!lst.empty()) {
lst.pop_back();
}
int main() {
139
return 0;
}
Explanation:
• benchmark vector: Measures the time taken to insert, iterate, and remove elements
from a std::vector.
• benchmark list: Measures the time taken to insert, iterate, and remove elements
from a std::list.
Interpreting Results:
• Memory Usage: Measure the memory footprint using tools like valgrind or custom
memory tracking.
Example Output:
Benchmarking std::vector...
std::vector time: 0.012345 seconds
Benchmarking std::list...
std::list time: 0.045678 seconds
Analysis:
• std::vector: Faster for insertion and iteration due to contiguous memory layout.
• std::list: Slower for insertion and iteration due to pointer overhead, but more
efficient for frequent insertions/deletions in the middle.
2. Google Benchmark
Google Benchmark is a powerful library for writing and running benchmarks. It provides
features like statistical analysis, parameterized benchmarks, and CSV output.
#include <benchmark/benchmark.h>
#include <vector>
#include <list>
#include <random>
BENCHMARK_MAIN();
Explanation:
• BENCHMARK: Registers the benchmark function and specifies the range of input sizes.
Output:
Analysis:
• Use Realistic Data: Use input data that reflects real-world scenarios.
• Profile Memory Usage: Measure memory usage to identify memory leaks or excessive
allocations.
6.2.7 Summary
• Performance Benchmarks: Essential for evaluating the efficiency of code and
identifying bottlenecks.
• Tools: Use libraries like <chrono> and Google Benchmark to measure and analyze
performance.
• Best Practices: Run multiple iterations, use realistic data, and compare against a baseline.
By conducting thorough performance benchmarks, you can make informed decisions about
optimizing your code and ensure that it meets performance requirements.
Appendices
144
145
• Practical Examples: Using iterators and ranges with STL algorithms and containers.
• Custom Deleters: How to define and use custom deleters with smart pointers.
• Examples: Practical scenarios demonstrating the use of smart pointers in STL contexts.
• Examples: Using lambdas with std::sort, std::for each, and other algorithms.
147
• Choosing the Right Container: Selecting the most efficient container for specific use
cases.
• Avoiding Common Pitfalls: Issues like iterator invalidation and unnecessary copies.
• Benchmarking and Profiling: Tools and techniques for measuring and improving
performance.
• Portability Tips: Writing portable STL code across different platforms and compilers.
• Online Resources: Websites, blogs, and forums for learning and discussion.
• Tools: Useful tools for C++ development, such as IDEs, debuggers, and profilers.
• Advanced Topics: Challenges involving smart pointers, allocators, and C++20 features.
150
• Definitions: Clear explanations of terms like iterators, allocators, functors, and ranges.
– ISO/IEC 14882:2020 (C++20 Standard): The latest official standard at the time of
writing.
– ISO/IEC 14882:2017 (C++17 Standard): For understanding features introduced in
C++17.
– ISO/IEC 14882:2014 (C++14 Standard): For backward compatibility and historical
context.
151
152
• Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template
Library by Scott Meyers:
– A practical guide to using the STL effectively, with tips and best practices.
• C++ Templates: The Complete Guide by David Vandevoorde, Nicolai M. Josuttis, and
Douglas Gregor:
– A deep dive into templates, which are foundational to understanding the STL.
• LearnCpp.com:
– A free online tutorial for learning C++ from the ground up, including STL topics.
• CppCon Talks:
– Annual C++ conference talks available on YouTube, covering advanced STL topics
and modern C++ features.
– Courses like ”C++ Standard Library” and ”Modern C++ Programming” for
intermediate learners.
– Courses such as ”C++: From Beginner to Expert” and ”Mastering C++ Standard
Library Features.”
– Research papers and articles on the design and implementation of the STL in major
standard library implementations (e.g., libstdc++, libc++, MSVC STL).
154
– An online tool for exploring how STL code is compiled and optimized by different
compilers.
• C++ Insights:
– A tool for visualizing how modern C++ features (e.g., lambdas, ranges) are
implemented under the hood.
• Valgrind:
– A memory profiling tool for detecting memory leaks and undefined behavior in
STL-based programs.
– Tools for identifying potential issues and improving code quality in STL-heavy
projects.
155
• Stack Overflow:
– A Q&A platform for asking and answering C++ and STL-related questions.
– Subreddits like r/cpp and r/learnprogramming for discussions and resource sharing.
– Online communities for real-time discussions and networking with other C++
developers.
– Local and virtual meetups for connecting with other C++ enthusiasts.
• Fluent C++:
– A blog focused on writing expressive and efficient C++ code using the STL.
156
– Insights from one of the leading figures in the C++ community, covering modern
C++ and STL topics.
• GitHub Repositories:
– Search for open-source projects that heavily use the STL to see practical
applications.
– Explore the source code of major STL implementations (e.g., libstdc++, libc++,
MSVC STL) to understand how they work internally.
157
• CppCon:
– The largest annual C++ conference, featuring talks on the STL and modern C++.
• Meeting C++:
• ACCU Conference:
– A conference for C++ developers, covering both beginner and advanced topics.
– A book that provides context for the development of C++ and the STL.
– Research papers by Alexander Stepanov, the creator of the STL, explaining its
design principles.
158
Practice Platforms
These platforms offer coding challenges and exercises to practice STL usage:
• LeetCode:
– Coding problems that can be solved using STL algorithms and containers.
• HackerRank:
• Codewars:
– A book that covers modern C++ features and their impact on performance.
159
– A set of guidelines maintained by Bjarne Stroustrup and Herb Sutter for writing
modern C++ code.
Miscellaneous Resources
Other useful resources for intermediate learners:
• C++ Podcasts:
– Podcasts like CppCast for staying updated on C++ news and trends.