Hello and welcome! Today's lesson explores a powerful feature of C++: storing functions in variables. This is useful for scenarios like callbacks, event handlers, or flexible program design. Our goal is to understand how to do this using C++ via function pointers and std::function
.
By the end of this lesson, you'll know how to store functions in variables using both traditional function pointers and the modern std::function
type.
Most importantly, we will learn to operate functions as objects, which will be very useful in future lessons.
First, let's look at function pointers, a classic C++ feature. A function pointer stores the address of a function, allowing dynamic behavior.
Consider a function to add two integers:
C++1int add(int a, int b) { 2 return a + b; 3}
To store this function in a pointer:
C++1#include <iostream> 2 3int add(int a, int b) { 4 return a + b; 5} 6 7int main() { 8 // Declare a function pointer 9 int (*fp)(int, int) = add; 10 11 // Use the function pointer 12 std::cout << "Using function pointer: " << fp(2, 3) << '\n'; // Output: Using function pointer: 5 13 14 return 0; 15}
Here, int (*fp)(int, int) = add;
declares a function pointer fp
that can point to any function taking two int
s and returning an int
. We then call fp(2, 3)
to use the add
function through the pointer.
Function pointers are also function objects because they can be called like ordinary functions. A function object in functional programming is essentially a function treated as a value, similar to how you treat numbers or strings. It means you can pass these functions around as arguments to other functions, return them as results from other functions, and assign them to variables.
Additionally, all types that can be implicitly converted to a function pointer are function objects, but this should be avoided. You should prefer proper function objects because they’re more powerful and easier to handle.
Next, we introduce std::function
, from the <functional>
header. std::function
is versatile and safer than raw function pointers, able to store any callable target.
Here's a comparison using std::function
for the add
function:
C++1#include <iostream> 2#include <functional> 3 4int add(int a, int b) { 5 return a + b; 6} 7 8int main() { 9 // Using std::function 10 std::function<int(int, int)> func = add; 11 12 std::cout << "Using std::function: " << func(2, 3) << '\n'; // Output: Using std::function: 5 13 14 return 0; 15}
Here, std::function<int(int, int)> func = add;
stores the add
function in a std::function
object, which improves type safety and flexibility.
- std::function:
std::function<int(int, int)> func = add;
creates astd::function
object storing any callable entity with theint(int, int)
signature. - Using std::function:
std::cout << "Using std::function: " << func(2, 3) << '\n';
callsadd
via thestd::function
objectfunc
, yielding the desired result:5
.
std::function
offers more flexibility and ease for storing various callable objects. However, it’s crucial to note that std::function
can introduce noticeable performance penalties. To be able to hide the contained type and provide a common interface over all callable types, std::function
uses a technique known as type erasure. Type erasure is usually based on virtual member function calls. Because virtual calls are resolved at runtime, the compiler can’t inline the call and has limited optimization opportunities.
Pros:
- Direct and simple method.
- Performance overhead is minimal.
Cons:
- Less type-safe.
- Limited flexibility in handling different callable entities.
Pros:
- Provides a versatile, type-safe way to store various callable entities.
- Can store functions, lambda expressions, and functors.
Cons:
- Performance overhead due to type erasure.
- Can obscure what's happening under the hood, making debugging more complex.
We explored how to store functions in variables using both function pointers and std::function
. We learned:
- Function pointers: Store function addresses but offer less safety and flexibility. They have minimal performance overhead but are limited in versatility.
std::function
: Provides a versatile, type-safe way to store various callable entities, though it can introduce performance penalties due to type erasure.
Now that you understand the theory and practical aspects, it's time for practice. In the next set of exercises, you'll try storing and using functions in variables using both methods. This hands-on practice is crucial to solidify your understanding. Let's get started!