Functora
In C++, a functor (also known as function object) is a class or struct that overloads the function call operator operator()
. This allows objects of the class to be used with the same syntax as a function call. Functors are powerful because they can maintain state between calls and can be used in many contexts where functions are expected, such as in standard algorithms.
#include <iostream>
class Multiplier {
private:
int factor;
public:
Multiplier(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
int main() {
Multiplier times3(3);
std::cout << "5 * 3 = " << times3(5) << std::endl;
std::cout << "7 * 3 = " << times3(7) << std::endl;
return 0;
}
Multiplier
class that stores a factor
.operator()
is overloaded to multiply its argument by the stored factor.main()
, we create a Multiplier
object with factor 3 and use it to multiply different numbers.#include <iostream>
#include <vector>
#include <algorithm>
class Accumulator {
private:
int sum;
public:
Accumulator() : sum(0) {}
void operator()(int x) {
sum += x;
}
int getSum() const {
return sum;
}
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
Accumulator acc = std::for_each(numbers.begin(), numbers.end(), Accumulator());
std::cout << "Sum of numbers: " << acc.getSum() << std::endl;
return 0;
}
Accumulator
class maintains an internal state (sum
).operator()
adds its argument to the internal sum.std::for_each
algorithm with Accumulator
to sum all elements in a vector.#include <iostream>
#include <vector>
#include <algorithm>
class DivisibleBy {
private:
int divisor;
public:
DivisibleBy(int d) : divisor(d) {}
bool operator()(int x) const {
return x % divisor == 0;
}
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int divisor = 3;
DivisibleBy isDivisibleBy3(divisor);
int count = std::count_if(numbers.begin(), numbers.end(), isDivisibleBy3);
std::cout << "Numbers divisible by " << divisor << ": " << count << std::endl;
return 0;
}
DivisibleBy
is a functor that checks if a number is divisible by a given divisor.std::count_if
with our functor to count numbers divisible by 3 in a vector.#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
class LengthCompare {
public:
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};
int main() {
std::vector<std::string> words = {"apple", "banana", "cherry", "date", "elderberry"};
std::sort(words.begin(), words.end(), LengthCompare());
std::cout << "Words sorted by length:" << std::endl;
for (const auto& word : words) {
std::cout << word << std::endl;
}
return 0;
}
LengthCompare
is a functor that compares strings based on their length.std::sort
to sort a vector of strings by length.A member function named count()
could indeed track state similarly to a functor's operator()
.
We explore the key advantages of functors over simple member functions, focusing on capabilities that go beyond just maintaining state.
While functors and member functions can both maintain state, functors offer distinct advantages in certain scenarios, particularly in the context of generic programming and when working with the C++ Standard Library algorithms.
#include <iostream>
#include <vector>
#include <algorithm>
class Adder {
private:
int value;
public:
Adder(int v) : value(v) {}
int operator()(int x) const { return x + value; }
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::transform(numbers.begin(), numbers.end(), numbers.begin(), Adder(10));
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
std::transform
.std::bind
) to achieve the same effect.#include <iostream>
#include <functional>
#include <vector>
class Multiplier {
private:
int factor;
public:
Multiplier(int f) : factor(f) {}
int operator()(int x) const { return x * factor; }
};
int main() {
std::vector<std::function<int(int)>> operations;
operations.push_back(Multiplier(2));
operations.push_back([](int x) { return x + 5; });
operations.push_back([](int x) { return x * x; });
for (const auto& op : operations) {
std::cout << op(10) << " ";
}
return 0;
}
std::function
objects, allowing for type erasure.#include <iostream>
template<typename Operation>
void performOperation(int x, Operation op) {
std::cout << "Result: " << op(x) << std::endl;
}
class Square {
public:
int operator()(int x) const { return x * x; }
};
class Cube {
public:
int operator()(int x) const { return x * x * x; }
};
int main() {
performOperation(5, Square());
performOperation(5, Cube());
return 0;
}
#include <iostream>
class EmptyFunctor {
public:
void operator()() const {}
};
template<typename Func>
class Container : private Func {
public:
void doSomething() {
Func::operator()();
}
};
int main() {
Container<EmptyFunctor> c;
std::cout << "Size of Container<EmptyFunctor>: " << sizeof(c) << std::endl;
return 0;
}
#include <iostream>
#include <functional>
template<typename F, typename G>
class Compose {
private:
F f;
G g;
public:
Compose(F f, G g) : f(f), g(g) {}
template<typename T>
auto operator()(T x) const -> decltype(f(g(x))) {
return f(g(x));
}
};
template<typename F, typename G>
Compose<F, G> compose(F f, G g) {
return Compose<F, G>(f, g);
}
int main() {
auto square = [](int x) { return x * x; };
auto addOne = [](int x) { return x + 1; };
auto squareThenAddOne = compose(addOne, square);
std::cout << "Result: " << squareThenAddOne(5) << std::endl;
return 0;
}
While it's true that member functions can maintain state similarly to functors, functors offer several unique advantages:
First-Class Function Behavior: Functors can be passed directly to algorithms and functions, behaving like first-class functions.
Type Erasure: They work seamlessly with std::function
, enabling flexible polymorphic behavior without inheritance.
Compile-Time Polymorphism: Functors excel in template-based generic programming, allowing for efficient compile-time polymorphism.
Empty Base Optimization: Stateless functors can be optimized away when used as base classes, which is not possible with member functions.
Function Composition: Functors naturally support function composition, allowing for the creation of complex operations from simpler ones.
STL Compatibility: They integrate more naturally with the C++ Standard Library algorithms and containers.
Functors in C++ are versatile and powerful constructs that combine the functionality of functions with the ability to maintain state. They are particularly useful in the following scenarios:
The examples provided demonstrate various use cases of functors, from simple arithmetic operations to more complex scenarios like custom sorting and accumulation. Functors offer better performance compared to function pointers because they can be inlined by the compiler. They also provide a clean and object-oriented way to encapsulate both behavior and associated data, making them a valuable tool in modern C++ programming.