C++: STL Function Objects
In lesson 37 we learned about iterators.
Now, we'll learn about the "soul" of STL algorithms — function objects.
Algorithms are the skeleton, function objects are the flesh. Only when combined do they unleash the true power of the STL.
1. Function Object Overview
(1) 1.1 What Are Function Objects?
Function objects (Functors) are objects that can be called like functions.
Three types of function objects:
- Function pointers
- Function object classes (overloading
operator()) - Lambda expressions (C++11)
(2) 1.2 Why Do We Need Function Objects?
STL algorithms are generic, but specific operations vary by need. Function objects let you customize operations.
Real-life analogy:
- Algorithm = washing machine (generic)
- Function object = laundry detergent (customizable: fresh scent / heavy duty / fabric softener)
2. Function Pointers
(1) 2.1 Basic Usage
Example: Custom sorting with function pointers (Difficulty ⭐⭐)
▶ Example 2: STL Container Usage (Difficulty ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
// Custom comparison function
bool compareDesc(int a, int b) {
return a > b; // Descending
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// Use function pointer
std::sort(v.begin(), v.end(), compareDesc);
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
Output:
3 1 4 1 5 9 2 6
Result:
9 6 5 4 3 2 2 1 1
💡 Tip:
- Function pointers are a C-language legacy; C++ recommends function objects or Lambda instead
3. Function Object Classes
(1) 3.1 What Are Function Object Classes?
Function object classes are classes that overload operator(), whose instances can be called like functions.
Example: Custom comparator (Difficulty ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
// Function object class: sort by string length
struct CompareByLength {
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};
int main() {
std::vectorstd::string words = {"apple", "banana", "cat", "dog"};
// Use function object
std::sort(words.begin(), words.end(), CompareByLength());
for (const auto& w : words) {
std::cout << w << " ";
}
std::cout << std::endl;
return 0;
}
Result:
cat dog apple banana
(2) 3.2 Advantages of Function Objects
| Comparison | Function Pointer | Function Object Class |
|---|---|---|
| State | No state | Can have state (member variables) |
| Performance | May not be inlined | Can be inlined, faster |
| Flexibility | Low | High (can be templated) |
(3) 3.3 Function Objects with State
Example: Counter function object (Difficulty ⭐⭐⭐)
#include <iostream>
#include <algorithm>
#include <vector>
// Function object: count elements satisfying a condition
struct Counter {
int threshold; // Threshold (state)
Counter(int t) : threshold(t) {}
bool operator()(int x) const {
return x > threshold; // Count elements greater than threshold
}
};
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
// Create function object, set threshold to 10
Counter counter(10);
// Count elements greater than 10
int count = std::count_if(v.begin(), v.end(), counter);
std::cout << "Elements greater than 10: " << count << std::endl; // Output: 2
return 0;
}
4. Lambda Expressions
(1) 4.1 What Are Lambda Expressions?
Lambda expressions are anonymous functions introduced in C++11 that can be defined inline wherever a function is needed.
Basic syntax:
[capture](parameters) -> return_type { body }
| Part | Description |
|---|---|
capture |
Capture list (capture external variables) |
parameters |
Parameter list |
return_type |
Return type (can be omitted) |
body |
Function body |
(2) 4.2 Basic Example
Example: Lambda sorting (Difficulty ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// Sort with Lambda expression (descending)
std::sort(v.begin(), v.end(), (int a, int b) {
return a > b;
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
Output:
3 1 4 1 5 9 2 6
(3) 4.3 Capture List
The capture list determines which external variables a Lambda can access.
| Capture Method | Description |
|---|---|
| `` | Capture nothing |
[x] |
Capture x by value |
[&x] |
Capture x by reference |
[=] |
Capture all variables by value |
[&] |
Capture all variables by reference |
[this] |
Capture the this pointer (used inside a class) |
Example: Lambda with state (Difficulty ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
int threshold = 10;
// Capture threshold by value
int count = std::count_if(v.begin(), v.end(),
[threshold](int x) {
return x > threshold;
});
std::cout << "Elements greater than " << threshold << ": " << count << std::endl;
return 0;
}
5. STL Predefined Function Objects
(1) 5.1 Arithmetic Function Objects
The functional header provides common function objects:
| Function Object | Purpose | Example |
|---|---|---|
std::plusT |
Addition | std::plusint() |
std::minusT |
Subtraction | std::minusint() |
std::multipliesT |
Multiplication | std::multipliesint() |
std::dividesT |
Division | std::dividesint() |
std::negateT |
Negation | std::negateint() |
Example: Doubling with multiplies (Difficulty ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Double all elements
std::transform(v.begin(), v.end(), v.begin(),
std::bind(std::multipliesint(), std::placeholders::_1, 2));
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 5.2 Comparison Function Objects
| Function Object | Purpose |
|---|---|
std::equal_toT |
Equal to |
std::not_equal_toT |
Not equal to |
std::greaterT |
Greater than |
std::lessT |
Less than |
std::greater_equalT |
Greater than or equal to |
std::less_equalT |
Less than or equal to |
(3) 5.3 Logical Function Objects
| Function Object | Purpose |
|---|---|
std::logical_andT |
Logical AND |
std::logical_orT |
Logical OR |
std::logical_notT |
Logical NOT |
6. Comprehensive Example
▶ Example 1: Score Processor (Difficulty ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
struct Student {
std::string name;
int score;
};
int main() {
std::vectorStudent students = {
{"Zhang San", 85},
{"Li Si", 92},
{"Wang Wu", 78}
};
// 1. Sort by score descending
std::sort(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score > b.score;
});
// 2. Find the highest score
auto max_it = std::max_element(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score < b.score;
});
std::cout << "Highest score: " << max_it->name << " " << max_it->score << std::endl;
// 3. Count passing students
int passed = std::count_if(students.begin(), students.end(),
(const Student& s) {
return s.score >= 60;
});
std::cout << "Students passed: " << passed << std::endl;
return 0;
}
Output:
Highest score:
Students passed:
❓ FAQ
Q: Which is better — Lambda or function object classes? A:- Simple operations → Lambda (concise code) - Complex operations / need reuse → Function object classes (better maintainability)
Q: Can
autodeduce a Lambda's type? A: A Lambda's type is a unique anonymous type; it can only be deduced withauto, not written as a concrete type.
auto func = (int x) { return x * 2; };
// std::function<int(int)> func = ... // Also works, but with performance overhead
Q: When should I use
std::function? A: Usestd::functionwhen you need to store function objects (as member variables, return values, etc.).
▶ Example 3: Lambda Expression (Difficulty ⭐)
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = 0;
std::for_each(v.begin(), v.end(), [&sum](int x) {
sum += x;
});
std::cout << "Total: " << sum << std::endl;
return 0;
}
Output:
1 2 3 4 5
[capture](parameters) { body }. [&sum] captures by reference, allowing modification of external variables.
| Topic | Key Points |
|---|---|
| Function pointers | Simple but limited functionality |
| Function object classes | Customizable operations, can carry state |
| Lambda | Anonymous functions, concise and powerful |
| Predefined function objects | std::plus etc., in functional |
| Capture list | How Lambda accesses external variables |
📖 Summary
- Function objects: classes overloading
operator(), callable like functions - Lambda: anonymous functions, syntax
[capture](parameters) { body } - std::function: generic function wrapper, can store any callable object
- std::bind: binds function arguments, generating new callable objects
📝 Exercises
-
Basic (Difficulty ⭐): Create a functor (a class overloading operator()) that "compares two integers", and test it with std::sort.
-
Intermediate (Difficulty ⭐⭐): Use std::function to store different types of callable objects (regular functions, lambdas, functors) and call them uniformly.
-
Challenge (Difficulty ⭐⭐⭐): Use std::bind to bind partial arguments, generating new callable objects. Implement a "argument pre-filling" function adapter.
- Function objects (functors): classes overloading operator()
- Function objects can hold state; regular functions cannot
- std::function: type-erased callable wrapper
- bind binds partial arguments to generate new callable objects
- Lambda is syntactic sugar for function objects
Next lesson: STL Adapters (#39)