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:
older_than(int limit)
initializes the age limit state to a specified value.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:
person_t(std::string name, int age)
to initialize the person’s name and age attributes.age()
and name()
, 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:
person_t
objects named people
.older_than
functor with a limit of 42.Functors are great for scenarios needing encapsulated, stateful operations tied to specific conditions or parameters. Common scenarios include:
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:
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!