Lesson 54
STL Algorithms and Iterators (part 3)
Covers common mistakes like dereferencing end() and forgetting the erase-remove idiom, best practices, when not to rely on STL algorithms, time complexity of common algorithms, and a cheat sheet.
Lesson content
Common mistakes and pitfalls
Even though STL algorithms simplify programming, beginners often make a few common mistakes.
1. Dereferencing end()
end() does not point to a valid element. It points one position past the last element. If the value isn't found, std::find() returns end(), and dereferencing it results in undefined behavior.
// Wrong
auto it = std::find(v.begin(), v.end(), 10);
std::cout << *it;
// Correct
auto it = std::find(v.begin(), v.end(), 10);
if (it != v.end())
{
std::cout << *it;
}2. Forgetting the erase-remove idiom
Many beginners expect std::remove() or std::remove_if() to erase elements from a container. std::remove() only rearranges elements and returns the new logical end. You must call erase() to actually remove them from the container.
// Wrong
std::remove(v.begin(), v.end(), 5);
// Correct
v.erase(
std::remove(v.begin(), v.end(), 5),
v.end()
);3. Using std::sort() on unsupported containers
std::sort() requires random access iterators. std::list provides bidirectional iterators, not random access iterators, so it has its own sort() member function.
// Wrong
std::list<int> numbers;
std::sort(numbers.begin(), numbers.end());
// Correct
numbers.sort();4. Modifying a container while iterating
Changing the size of a container during iteration can invalidate iterators. The correct approach is to collect changes first, or use an algorithm designed for safe modification.
// Wrong
for (auto it = numbers.begin();
it != numbers.end();
++it)
{
numbers.push_back(100);
}5. Writing loops instead of using algorithms
Using algorithms often results in code that is easier to read and maintain than manual loops.
// Less idiomatic
for (int i = 0; i < values.size(); i++)
{
values[i] *= 2;
}
// Preferred
std::transform(
values.begin(),
values.end(),
values.begin(),
[](int value)
{
return value * 2;
}
);Best practices
- Prefer STL algorithms over manual loops when an appropriate algorithm exists
- Use auto for iterator declarations to improve readability
- Always verify an iterator before dereferencing it
- Prefer range-based for loops for simple traversal
- Use const_iterator or cbegin()/cend() when you don't need to modify elements
- Use lambda expressions for short, localized behavior
- Capture only the variables needed in lambdas
- Understand iterator invalidation rules for each container
- Choose the right container for your algorithm (e.g., std::vector for frequent sorting, std::list for frequent insertions/removals)
When not to use STL algorithms
Although STL algorithms are powerful, there are cases where another approach is more suitable. Remember that clarity is often more valuable than cleverness.
- The algorithm you need doesn't exist in the STL
- The operation requires complex state that makes the algorithm call difficult to understand
- A container provides a more efficient member function (e.g., list::sort())
- Readability suffers because chaining multiple algorithms becomes overly complex
- Performance profiling indicates that a custom implementation is necessary (only after measuring)
Time complexity of common algorithms
Understanding algorithm complexity helps you write efficient programs. Note that std::binary_search requires the range to be sorted before it is called.
- std::find – O(n)
- std::find_if – O(n)
- std::count – O(n)
- std::count_if – O(n)
- std::for_each – O(n)
- std::copy – O(n)
- std::transform – O(n)
- std::reverse – O(n)
- std::sort – O(n log n)
- std::binary_search – O(log n)
Summary
- The Standard Template Library (STL) provides reusable components for common programming tasks
- Algorithms perform operations such as sorting, searching, counting, copying, and transforming data
- Iterators provide a uniform way to access elements across different container types
- Algorithms work on iterator ranges, usually represented by [begin(), end())
- Iterator categories determine which operations are supported
- Frequently used algorithms include std::sort, std::find, std::find_if, std::count, std::count_if, std::for_each, std::reverse, std::copy, std::transform, and std::remove_if
- Always check iterators before dereferencing them
- Use the erase-remove idiom with sequence containers like std::vector
- Prefer algorithms over handwritten loops when they clearly express your intent
Key takeaways
- Algorithms operate on ranges defined by iterators
- Iterators behave like generalized pointers
- begin() points to the first element; end() points one past the last
- std::sort() requires random access iterators
- std::find() returns end() if the value isn't found
- std::remove_if() should typically be followed by erase()
- Use auto for cleaner iterator declarations
- Use const_iterator when elements shouldn't be modified
- Prefer STL algorithms to manual loops when appropriate
- Learn the complexity of common algorithms to write efficient code
Frequently asked questions
What is the difference between an algorithm and an iterator?
An algorithm performs an operation (such as sorting or searching), while an iterator points to elements in a container and defines the range on which the algorithm operates.
Why don't algorithms work directly with containers?
Algorithms are designed to be generic. By working with iterators instead of containers, the same algorithm can operate on many different container types, such as std::vector, std::deque, and std::list (when the required iterator category is supported).
When should I use a range-based for loop instead of an iterator?
Use a range-based for loop when you simply need to visit each element. Use explicit iterators when you need to pass a range to an algorithm, need fine-grained control over traversal, or are working with algorithms that return iterators.
Why does std::sort() not work with std::list?
std::sort() requires random access iterators, but std::list provides only bidirectional iterators. Use the container's list::sort() member function instead.
Why should I use STL algorithms instead of writing loops?
STL algorithms are well-tested, efficient, expressive, less error-prone, and easier for other C++ developers to understand.
Cheat sheet: common iterator functions
- begin() – first element
- end() – one past the last element
- cbegin() – constant iterator to first element
- cend() – constant iterator to end
- rbegin() – last element (reverse iterator)
- rend() – before the first element (reverse iterator)
Cheat sheet: frequently used algorithms
- std::sort – sort elements
- std::find – find a value
- std::find_if – find using a condition
- std::count – count matching values
- std::count_if – count values satisfying a condition
- std::for_each – apply a function to each element
- std::reverse – reverse elements
- std::copy – copy a range
- std::transform – transform elements
- std::remove_if – remove matching elements (with erase)
- std::binary_search – search a sorted range
- std::min_element – find the smallest element
- std::max_element – find the largest element
- std::accumulate – sum or combine values (defined in <numeric>, not <algorithm>)
Conclusion
STL Algorithms and Iterators in C++ are essential tools for writing modern, efficient, and maintainable code. Algorithms encapsulate common operations like sorting, searching, and transforming data, while iterators provide a consistent way to access elements across different containers.
By understanding iterator categories, choosing the right algorithm, and following best practices such as checking iterators before dereferencing and using the erase-remove idiom correctly, you'll write cleaner and more robust C++ programs.
Mastering these concepts will also make it easier to learn more advanced areas of modern C++, including ranges, parallel algorithms, and generic programming.