Welcome to the lesson on creating functional objects in C++! In this session, we'll explore why functional objects are vital in modern C++ programming. You'll learn how they encapsulate behavior within objects for more modular, reusable code. Our aim is to understand what functional objects are and how to create and use them through practical examples.
Functional objects are objects you can call like functions. Essentially, they encapsulate a function's logic within an object. This is particularly useful for passing functions as arguments, storing them, or configuring them with state information.
Unlike standard functions, which cannot hold state, functional objects can maintain state because they are object-oriented. This allows them to keep information across function calls, enabling more complex behavior than a simple function pointer or lambda expression can provide.
Let's start by designing a basic class for our functional object. We'll set up a constructor for state initialization and define the callable operator, operator()
, to make the object callable like a function. Note that in C++ it is common to call such objects "functors". We will call them this way through this course as well.
However, in general functional programming theory, "functor" is usually used to describe a more general object, which we will talk about in the next course of this path.
Consider creating a functor to filter people based on age:
C++1#include <string> 2 3class older_than { 4public: 5 // Constructor to initialize the age limit state 6 older_than(int limit) : m_limit(limit) {} 7 8 // Callable operator method that compares the person's age to the limit 9 bool operator()(const person_t& person) const { 10 return person.age() > m_limit; 11 } 12 13private: 14 int m_limit; // State to store the age limit 15};
Here:
- The constructor
older_than(int limit)
initializes the age limit state to a specified value. - The
operator()
method makes the object callable like a function and contains the logic to check if a person's age is greater than the limit.
Now, let’s break down the functional objects and their supporting classes in a full example. First, define a class to represent a person:
C++1#include <string> 2 3class person_t { 4public: 5 // Constructor to initialize person's name and age 6 person_t(std::string name, int age) : m_name(name), m_age(age) {} 7 8 // Getter method for the age attribute 9 int age() const { return m_age; } 10 11 // Getter method for the name attribute 12 std::string name() const { return m_name; } 13 14private: 15 std::string m_name; // Private member to store the person's name 16 int m_age; // Private member to store the person's age 17};
This class has:
- A constructor
person_t(std::string name, int age)
to initialize the person’s name and age attributes. - Getter methods,
age()
andname()
, to access these attributes, ensuring encapsulation.
Next, let’s complete our main function to demonstrate filtering persons by age using the older_than
functor:
C++1#include <vector> 2#include <string> 3#include <boost/range/adaptor/filtered.hpp> 4#include <boost/range/algorithm/for_each.hpp> 5#include <iostream> 6 7int main() { 8 // Create a vector of person_t objects with different ages 9 std::vector<person_t> people = { 10 {"Alice", 45}, 11 {"Bob", 32}, 12 {"Charlie", 15}, 13 {"David", 55} 14 }; 15 16 // Instantiate the older_than functor with an age limit of 42 17 older_than older_than_42(42); 18 19 // Use the older_than functor to filter the vector of people 20 auto filtered_people = people | boost::adaptors::filtered(older_than_42); 21 22 // Print the names of people older than 42 23 boost::for_each(filtered_people, [](const person_t& person) { 24 std::cout << person.name() << " is older than 42.\n"; 25 }); 26 27 return 0; 28} 29/* Output: 30Alice is older than 42. 31David is older than 42. 32*/
In this example:
- We created a list of
person_t
objects namedpeople
. - We instantiated the
older_than
functor with a limit of 42. - We used the functor to filter the list and print the names of people older than 42 using the Boost library’s adaptors and for_each utility functions.
Functors are great for scenarios needing encapsulated, stateful operations tied to specific conditions or parameters. Common scenarios include:
- Custom sorting or filtering criteria (as shown in our example).
- Parameterized actions like transformations on data or predicates for conditions.
- Complex algorithms where configuration parameters might change, but the core logic remains the same.
In our example, we used a functor to create a filtering mechanism for a list of persons based on age, demonstrating how functors can encapsulate logic and state effectively.
To make functors more versatile, you can use std::function
from the C++ Standard Library. This template class can encapsulate any callable target, such as functors, function pointers, or lambda expressions, providing greater flexibility.
C++1#include <vector> 2#include <string> 3#include <boost/range/adaptor/filtered.hpp> 4#include <boost/range/algorithm/for_each.hpp> 5#include <iostream> 6 7int main() { 8 // Create a vector of person_t objects 9 std::vector<person_t> people = { 10 {"Alice", 45}, 11 {"Bob", 32}, 12 {"Charlie", 15}, 13 {"David", 55} 14 }; 15 16 // Use std::function to abstract the callable type 17 std::function<bool(const person_t&)> older_than_42 = older_than(42); 18 19 // Iterate through the people vector and print names of those older than 42 20 for (const auto& person : people) { 21 if (older_than_42(person)) { 22 std::cout << person.name() << " is older than 42.\n"; 23 } 24 } 25 26 return 0; 27} 28/* Output: 29Alice is older than 42. 30David is older than 42. 31*/
Here, std::function<bool(const person_t&)>
abstracts the callable target, providing more flexibility and allowing us to easily switch between different callable types without changing the main logic.
You've learned the fundamentals of creating functional objects (functors) in C++. We covered:
- What functors are and their usefulness.
- Designing and implementing a functor in C++ with a practical example.
- Enhancing usability with
std::function
.
Now it's time to put theory into practice! You will move to a hands-on exercise session where you'll create and use functional objects to reinforce the concepts learned in this lesson. Ready to get started? Let's dive in!