5.9. Pointers to functions¶
In C++ you can point to anything with an address:
Global variables
Stack and free store variables
Functions
When a function is called, an activation record for the function is pushed onto the runtime stack. This means every function has an address.
Consider the following code:
#include <iostream>
int foo() {
return 5;
}
int main() {
std::cout << foo;
}
What does this program print?
Hint: It doesn’t call the function foo
It prints the address of the function named foo
When a function is called using operator()
(the function call operator),
execution jumps to the address of the function being called.
We can make use of this to store the address of the function.
To declare a pointer to a function that returns an int
and takes no parameters:
int (*pf)();
The pointer variable is named pf
.
The parentheses around (*pf)
are required due to operator precedence.
Without the parentheses:
int *pf(); // not a pointer to a function
// same as above
int* pf();
Instead, this declares a function that returns an int*
and takes no parameters.
To declare a pointer named func
pointing to a function that returns a double
and takes two parameters:
double (*func)(int x, int y);
Once you have a valid function pointer definition, you can assign functions to it.
Given the following functions:
double add (int x, int y) { return x+y;}
double multiply(int x, int y) { return x*y;}
double pi () { return 3.14159265;}
We can define a pointer to our functions
This is legal, but not preferred, since our pointer is undefined.
double (*func)(int, int);
When you can, initialize variables with a value.
double (*func)(int, int) = add;
A downside to traditional function pointer initialization is that this doesn’t look like the initialization syntax we are used to. This is a legacy of the C language C++ was originally based on.
The C++11 type alias allows defining a name that refers to a previously defined type:
double (*func)(int, int); // old syntax
using func = double(*)(int, int); // since C++11
Try This!
Refactor the previous example to replace the traditional C syntax
with the C++11 using
type alias.
5.9.1. Example: Caesar ciphers¶
A simple substitution cipher, called ROT13, short for ‘rotate 13 places’ can be used to obfuscate text by replacing each letter with the letter 13 letters after it in the alphabet. It is a special case of the Caesar cipher, used in ancient Rome to obscure communication between Julius Caesar and his generals. A related variation called ROT47 extends the idea of ROT13 to include numbers and common symbols.
Suppose we want to create a program that allows users to run either ROT13 or ROT47? There are many ways to implement such a program. This example demonstrates how to use function pointers to dynamically control at which function is called within a loop at runtime.
Often, when writing a program, it is useful to start at ‘the top’. Suppose we want a simple command line program that takes 4 basic inputs:
- -h
A command line switch to show help.
- -l
A command line switch to transform only letters in the Latin alphabet. This switch will enable the ROT13 function.
- -f
A command line switch to transform the full set of printable letters ASCII character set. This switch will enable the ROT47 function.
- standard input
This is where the program will get the text to work on.
Once we have decided on this as our basic framework,
we can create a file like help.h
:
#pragma once
#include <iostream>
static void usage(const char* name) {
std::cerr << "Encrypt or decrypt text read from standard input\n"
<< "Usage: " << name << " [-h|l|f]\n";
}
static void help (const char* name) {
usage(name);
std::cerr << "Options:\n"
<< " -h Show this text and exit.\n"
<< " -l En(de)crypt using the Latin characters ([A-Z,a-z]\n"
<< " (default)\n"
<< " -f En(de)crypt using the Full set of "
<< "printable ASCII characters\n"
<< "\nOnly 1 of any option can be meaningfully specified.\n"
<< "\nThe last of either '-l' or 'f' provided is used.\n"
<< "\nRunning with no input from standard in enters\n"
<< "'interactive mode':\n"
<< " - Text can be entered one message per line.\n"
<< " - The program runs until 'CTRL+C' is entered or "
<< " EOF is reached.\n\n"
<< "Running on plain text creates cipher text\n"
<< "Running on cipher text creates plain text\n\n";
exit(0);
}
The ‘fundamental unit’ of any text input is a char
,
so it makes sense to write our transforming functions to work with a single
character at a time.
We will write three functions for this program, one for ROT13 and one for ROT47.
The third function takes a std::string
and a function pointer as input,
and transforms the string using the provided function,
one character at a time.
First we declare our interfaces:
#pragma once
//
// Function to perform simple character rotations through an alphabet.
//
// !! DO NOT use to encrypt sensitive data!!
// These functions are easily decrypted, even using pencil and paper.
//
#include <string>
// A pointer to the function that will transform a character
using transform = char (*)(const unsigned char c);
// Rotate a character 13 places in the alphabet.
//
// This function assumes a basic 26 letter Latin alphabet
//
// If the character received is not within the range [A-Z, a-z],
// then the character is returned unchanged.
char rot13(const unsigned char c);
// Rotate a character 47 places in the set of printable ASCII characters
//
// If the character received is not a 'printable' character (value < 33),
// then the character is returned unchanged.
char rot47(const unsigned char c);
// Use a 'character handler' (rot13 or rot47) to
// transform a message 1 character at a time.
void render_text(std::string message, transform handler);
The using declaration
exists only to simplify our use of our function pointer.
Any place you see the word transform
,
you can literally replace it with
char (*)(const unsigned char c)
and not change how the program behaves.
We implemented our functions to take type unsigned char
because
they depend on the library functions std::isdigit
and std::isalpha
.
These function have undefined behavior if the character provided
is not an unsigned char
.
With these definitions in place, we can implement them:
#include "caesar.h"
#include <iostream>
#include <cctype> // isalpha, islower
#include <string>
// TODO: add locale and extend non english alphabets
char rot13(const unsigned char c) {
if (!std::isalpha(c)) return c; // if not a Latin letter,
// then return the current char
// in order to rotate upper or lower case
// need to know where the alphabet 'starts'
const char start = std::islower(c) ? 'a' : 'A';
return (c - start + 13) % 26 + start;
}
char rot47(const unsigned char c) {
// first printable character = 33 = '!'
static constexpr char start = '!';
if (c < start) return c;
return (c - start + 47) % 94 + start;
}
void render_text(std::string message, transform handler) {
for (const auto& c: message) { // extract each char from the string
std::cout << handler(c); // print transformed character
}
std::cout << std::endl; // print newline and flush stream
}
And now we can put it all together from a small main program.
One thing to note in this main is that it uses both standard input and command-line arguments.
The difference is a common source of confusion.
#include "caesar.h"
#include "help.h"
#include <cstring> // strcmp
#include <iostream>
#include <string>
int main(int argc, char** argv) {
// Define a default a 'character handler'
// the variable 'handler' provides an option to
// change the encryption function at runtime
transform handler = rot13;
// loop on command line argumenats
// call help and exit or
// use of the 2 transforms
// or reject the input and exit
for (int i=1; i < argc; ++i) {
if (!std::strcmp(argv[i], "-h")) {
help(*argv);
} else if (!std::strcmp(argv[i], "-l")) {
handler = rot13;
} else if (!std::strcmp(argv[i], "-f")) {
handler = rot47;
} else {
usage(*argv);
exit(-1);
}
}
std::string message;
while (getline(std::cin, message)) {
render_text(message, handler);
}
return 0;
}
Note, we did not use the function call operator, operator()
when
assigning values to handler
.
The name rot13
points to the address where the function rot13
is stored.
Try This!
Consider compiling these files in your own environment and experimenting with variations.
Why are 13 and 47 common rotations?
Try to think of other rotations and implement them as additional program options.
More to Explore
Caesar cipher from Wikipedia.
From: cppreference.com: