Extended Types of Pointers II
Utilizing void pointers and function pointers for generic and dynamic programming.
This lesson covers the following two types of pointer and their applications:
Void pointer
Function pointer
The void pointer
In C++, a void pointer is a special pointer type that can hold the address of any type of object but doesn't provide information about the type itself. It is often used when dealing with generic or unknown types. In this part, we explore the syntax and usage of void pointers with a simple example.
A void pointer is declared using the void*
type. Its generic syntax is as follows:
void* vptr = any_address.
Here’s an example that demonstrates the usage of a void pointer:
#include <iostream>using namespace std;int main(){char * msg = new char[15] {'A','n', 'n', 'a'};void * vptr = msg; // or &msg[0]cout << "&msg[0]: " << vptr << " and string is: "<< msg <<endl<< "or it also be printed through: "<<endl<< "&msg[0]: " << (void*)&msg[0] << " " << &msg[0] << endl // will print "Anna"<< "&msg[1]: " << (void*)&msg[1] << " " << &msg[1] << endl; // will print "nna"return 0;}
In this code, a character pointer
msg
is dynamically allocated to store an array of characters with a size of15
. The characters'A', 'n', 'n', 'a', '\0', '\0', ...
are initialized in the array. When we usecout
to print an address corresponding to a character data type, C++ considers it a null-terminated string and prints the characters until it encounters the'\0'
character. To print the address of a character, it is best to cast it tovoid*
, which we've done in the above code to ensure the address is printed correctly.
Example: Writing generic swapping
Let’s witness the versatility of the generic_swap
function, an application of void*
that allows us to perform value swapping across different data types. From integers to doubles, characters, and even same-sized arrays, this powerful function showcases the magic of generic programming.
#include <iostream>using namespace std;void generic_swap(void* ptr1, void* ptr2, size_t size){char* charPtr1 = static_cast<char*>(ptr1);char* charPtr2 = static_cast<char*>(ptr2);for (size_t i = 0; i < size; i++){char temp = charPtr1[i];charPtr1[i] = charPtr2[i];charPtr2[i] = temp;}}void printArray(int A[], int size){for(int i=0; i<size; i++)cout << A[i] << " ";}int main(){int inum1 = 5;int inum2 = 10;cout << "Before swapping (two integers): num1 = " << inum1 << ", num2 = " << inum2 << endl;generic_swap(&inum1, &inum2, sizeof(int));cout << "\tAfter swapping (two integers):: num1 = " << inum1 << ", num2 = " << inum2 << endl;double dnum1 = 5;double dnum2 = 10;cout << "Before swapping (two doubles): num1 = " << dnum1 << ", num2 = " << dnum2 << endl;generic_swap(&dnum1, &dnum2, sizeof(double));cout << "\tAfter swapping (two doubles):: num1 = " << dnum1 << ", num2 = " << dnum2 << endl;char cnum1 = 'A';char cnum2 = 'B';cout << "Before swapping (two characters): num1 = " << cnum1 << ", num2 = " << cnum2 << endl;generic_swap(&cnum1, &cnum2, sizeof(char));cout << "\tAfter swapping (two characters):: num1 = " << cnum1 << ", num2 = " << cnum2 << endl;int A[] = {1,2,3,4,5};int B[] = {6,7,8,9,10};cout << "Before swapping (two Array): A = { "; printArray(A, 5);cout << "} B = { ";printArray(B, 5); cout << "}";generic_swap(A, B, sizeof(A));cout << "\n\tAfter swapping (two Array): A = { "; printArray(A, 5);cout << "} B = { ";printArray(B, 5); cout << "}";return 0;}
The generic_swap()
function is a powerful swapping function that allows for swapping values of different data types. It achieves this flexibility by using void
pointers as input parameters.
Lines 3–14:The function takes two void pointers (
ptr1
andptr2
) and the size of the data type (size
) as input. It then casts the void pointers tochar*
pointers to enable byte-level swapping. The function uses a loop to iterate over each byte in the memory block of the given size. During each iteration, it swaps everyi
th byte ofptr1
andptr2
by storing the value ofcharPtr1[i]
in a temporary variabletemp
, assigningcharPtr2[i]
tocharPtr1[i]
, and finally, assigning the temporary value tocharPtr2[i]
. This byte-level swapping ensures that the values stored inptr1
andptr2
are swapped correctly, regardless of the data type.Lines 20–55:In the
main()
function, thegeneric_swap()
function is demonstrated by swapping integers, doubles, characters, and arrays of integers. The function is called with appropriate arguments, such as the addresses of the variables and the size of the respective data types. The output shows the values before and after the swapping process, demonstrating that the function successfully swaps the values regardless of the data type.
The generic_swap()
function provides a convenient and powerful way to swap values of different types, making it versatile and reusable across various scenarios.
Function pointers
Have you ever made a small mistake similar to the one shown below?
#include <iostream>using namespace std;void function1(int param){cout << "Hello...! I am doing nothing" << param << endl;}void function2(int param){cout << "Hello...! I am doing nothing" << param << endl;}int main(){// your code goes herecout << function1 << endl; // You forgot to pass the argument and parentheses ()cout << (void*)function1 << endl; // You forgot to pass the argument and parentheses ()cout << function2 << endl; // You forgot to pass the argument and parentheses ()cout << (void*)function2 << endl; // You forgot to pass the argument and parentheses ()return 0;}
Tip: Run the code above, and you’ll see some very long addresses (if you’re running the code on Visual Studio) or a boolean value
1
(in lines 16 and 19, we have explicitly cast it tovoid*
). What is that address doing here?
Remember: It's important to remember that every function name in a program represents the address of its corresponding location in memory where the machine code (as 0s and 1s) of that function resides.
Function pointers in C++ allow us to store and manipulate the memory addresses of functions. They provide a way to treat functions as variables, allowing us to pass functions as arguments to other functions, store them in data structures, and call them dynamically at runtime. The syntax for declaring a function pointer is as follows:
return_type (*pointer_name)(arguments);
Here, return_type
represents the function’s return type, the function pointer’s name, and the arguments expected by the function.
In this section, we'll explore the application of function pointers with examples.
Example: Simple calculator
Let’s consider a simple calculator program that performs basic arithmetic operations. We can use function pointers to switch between different operations based on user input.
Here’s the implementation:
#include <iostream>using namespace std;// Function declarations for arithmetic operationsint add(int a, int b);int subtract(int a, int b);int multiply(int a, int b);int divide(int a, int b);int main() {int a, b;char op;int (*operation)(int, int); // Function pointer declarationcout << "Enter two numbers: ";cin >> a >> b;cout << "Enter an operator (+, -, *, /): ";cin >> op;// Assign the appropriate function pointer based on user inputswitch (op){case '+':operation = add; // operation is assigned the address of 'add()' functionbreak;case '-':operation = subtract; // operation is assigned the address of 'subtract()' functionbreak;case '*':operation = multiply; // operation is assigned the address of 'multiply()' functionbreak;case '/':operation = divide; // operation is assigned the address of 'divide()' functionbreak;default:cout << "Invalid operator!";return 0;}// Perform the arithmetic operation using the function pointerint result = operation(a, b);cout << "Result: " << result << endl;return 0;}// Function definitions for arithmetic operationsint add(int a, int b){return a + b;}int subtract(int a, int b){return a - b;}int multiply(int a, int b){return a * b;}int divide(int a, int b){if (b != 0){return a / b;} else{cout << "Cannot divide by zero!";return 0;}}
Enter the input below
Note: Please enter any two numbers followed by the operator as input. For example:
2 3 +
,2 3 *
, or2 3 /
.
The code declares four functions (
add()
,subtract()
,multiply()
, anddivide()
) to perform different arithmetic operations.In the
main()
function, the user is prompted to enter two numbers and an operator.A function pointer named
operation
is declared, which can point to any of the four arithmetic functions.The appropriate function pointer is assigned using a
switch
statement based on the user’s input.The selected arithmetic operation is performed using the function pointer, storing the result in the
result
variable.Finally, the result is printed to the console.
Let’s look at another nice example that’ll help us solve a more interesting problem.
Example: Sorting based on comparison functions
Let’s implement a sorting function that sorts an array of integers in ascending or descending order based on a comparison.
Before knowing about pointers, what would we have done?
Implementing a sorting function for arrays of integers is a common task, but typically, we would need separate functions for sorting in ascending and descending order. As the complexity of the data increases, maintaining separate sorting functions becomes cumbersome. For instance, consider a more complex dataset like student records (we’ll talk about such records in the upcoming lessons), where each column requires a different kind of comparison. Writing separate sorting functions for each attribute would be impractical and inefficient.
However, by harnessing the power of function pointers, we can create a single generic sorting function that accepts a comparison function as a parameter. This enables us to sort the array based on the passed function without the need to know the specific comparison being performed. Function pointers allow us to dynamically bind the appropriate comparison function at runtime, resulting in a more powerful and elegant solution. Let’s see how we can do that:
#include <iostream>using namespace std;// compare functionint compare_a (int a, int b){return(a>b);}int compare_d (int a, int b){return(a<b);}void sorting(int numArray[], int size, int (*compare)(int, int)){for(int i = 0; i<size - 1; i++)for(int j = 0; j < size - i - 1; j++)if(compare(numArray[j], numArray[j+1]))swap(numArray[j], numArray[j+1]);}int main() {// your code goes hereint numArray[10] = {5,2,3,2, 1, 10, 5, 8,4,7};int (*compare[])(int, int) = {compare_a,compare_d};// an array of function pointers,// where [0]'s hold compare_a's address and [1]'s hold compare_dint size = 10;sorting(numArray, size/2, compare[0]); // sorting first half in ascendingsorting(numArray + size/2, size-size/2, compare[1]); // sorting 2nd half in descendingprint(numArray, size);return 0;}
Lines 5–8:
compare_a()
returnstrue
when the two passed values (a
andb
) violate the ascending condition (a <= b
), indicating the need for swapping.Lines 9–12: Similarly,
compare_d()
checks the descending condition.Line 23: We have made
compare[]
an array of function pointers, where the first index has the address ofcompare_a
and the second index has the address ofcompare_d
.Lines 27–28: The sorting function is called, and
compare[0]
andcompare[1]
are passed as arguments. This allows for dynamic decision-making within thesorting()
function, where the first call on line 17 invokescompare_a()
, and the second call invokescompare_d()
. This flexible implementation enables the function to adapt to different comparison requirements.
Applications of function pointers
There are several applications of function pointers, including the following:
Callback mechanism: Function pointers are often used in callback mechanisms, where a function can accept a function pointer as an argument. This allows the caller to specify a function that will be called back by the callee at a specific point in the program execution. This mechanism is commonly used in event-driven programming, where functions are triggered in response to specific events.
Dynamic function selection: Function pointers can be used to dynamically select and invoke functions at runtime. Instead of using conditional statements or switch-case statements to determine which function to call, a function pointer can be used to directly invoke the desired function based on certain conditions or inputs. This provides flexibility and allows for dynamic behavior in the program.
Function wrappers and decorators: Function pointers can be used to create function wrappers or decorators, which modify or enhance other functions’ behavior. Passing function pointers as arguments to wrapper functions makes it possible to add additional functionality to existing functions without modifying their code. This is commonly used in areas such as logging, error handling, and performance monitoring.