Programming Workshop 2 (CSCI 1061U)
Faculty of Science, Ontario Tech University
Consider the following file
square.cpp
#include <iostream>
using std::cout;
using std::endl;
int square(int x)
{return x*x;
}
int main()
{int (*fnptr)(int);
fnptr = square;
2) << endl;
cout << fnptr(
return 0;
}
When we compile and excute this file, we get the following
> g++ square.cpp -o square
> ./square
4
Here statement int (*fnptr)(int)
declares a variable fnptr
that can store a pointer to a function that takes one int
argument and returns an int
. In the above program square
is such a function. Function square
takes one int
argument and returns an int
. We can assign the address of this function to variable fnptr
using the assignment fnptr = square
. Note that the name of the function is also its address. So, we don’t need to use the &
to get the address of this function. We can then call the function square
by using the variable fnptr
.
Consider the following function
void swap(float* a, float* b)
{float tmp;
tmp = *a;
*a = *b;
*b = tmp; }
We can declare a variable to store the pointer to function swap
as follows
void (*m)(float* a, float* b);
We can then assign the address of function swap
to variable m
as follows
m = swap;
Consider the following function
bool gt(int a, int b)
{return a > b;
}
We can declare a variable to store the pointer to function gt
as follows
bool (*l)(int a, int b);
We can then assign the address of function gt
to variable l
as follows
l =gt;
Consider the following function
double sum(double a[], int i, int j, int s)
{double sum=0.0;
for (int k=i; k<=j; k+=s) {
sum += a[k];
}return sum;
}
We can declare a variable to store the pointer to function sum
as follows
double (*n)(double a[], int i, int j, int s)
We can then assign the address of function sum
to variable n
as follows
n = sum;
Having variables that can store pointers to functions allow us to pass functions as arguments to other functions. These is a very powerful feature that enables us to write code where processing can change on the fly.
Consider the following code. Here do_some_process
is a function that relies upon the function passed to it as its argument to do the actually processing. This means that we can change the behavior of do_some_process
by passing a different function at runtime.
function-ptr-arguments.cpp
#include <iostream>
using std::cout;
using std::endl;
int square(int x)
{return x*x;
}
int neg(int x)
{return -x;
}
int do_some_process(int (*fn)(int), int x)
{return fn(x);
}
int main()
{int x = 5;
cout << do_some_process(square, x) << endl;
cout << do_some_process(neg, x) << endl;
return 0;
}
When we compile and run this code, we get the following
> g++ function-ptr-arguments.cpp -o prog
> ./prog
25
-5
Function pointers are frequently used to set up listener or callback functions. These function are invoked when a certain event occurs. These provide an easy way for a user to inject their own functionality into a larger system. To reiterate this pointe, function pointers are often used to pass around processing instructions.
Below you see an example of a callback function for the GLUT
library that allows a user to respond to mouse events.
void glutMouseFunc(void (*func)(int button, int state, int x, int y));
It is also possible to avoid explicit function pointers by using virtual functions and polymorphism. Virtual function, however, are implemented behind the scene using function pointers.
In addition to function pointers seen above, C++ also provides functors. Functors are objects that can be used as if these are functions. Functors are more powerful than good’ol function pointers, since functors can carry around state.
Consider the code below:
functors.cpp
#include <iostream>
using std::cout;
using std::endl;
class Square {
public:
int operator()(int x) {
return x*x;
}
};
int main()
{
Square a;
3) << endl;
cout << a(
return 0;
}
Square
is a class. However, we have overloaded its ()
operator, which allows us to use it as if it is a function. In the above code instance a
of Square
behaves as if it is a function—a(3)
.
Functors are simply classes, and we can use templates to pass a functor as an argument.
#include <iostream>
using std::cout;
using std::endl;
class Square {
public:
int operator()(int x) {
return x*x;
}
};
class Neg {
public:
int operator()(int x) {
return -x;
}
};
template<typename T>
int do_some_process(T process, int x)
{return process(x);
}
int main()
{
Square a;
Neg b;
2) << endl;
cout << do_some_process(a, 2) << endl;
cout << do_some_process(b,
return 0;
}
An advantage of using the functors way of defining function arguments template<typename T> int do_some_process(T process, int x) { ... }
is that it also allows us to pass in functions as below
#include <iostream>
using std::cout;
using std::endl;
class Square {
public:
int operator()(int x) {
return x*x;
}
};
class Neg {
public:
int operator()(int x) {
return -x;
}
};
int timestwo(int x)
{return 2*x;
}
template<typename T>
int do_some_process(T process, int x)
{return process(x);
}
int main()
{
Square a;
Neg b;int x = 9;
cout << do_some_process(a, x) << endl;
cout << do_some_process(b, x) << endl;
cout << do_some_process(timestwo, x) << endl;
return 0;
}
C++11 standard introduces lambda functions. These are noname functions that can be defined and called at the same time. They are used extensively in some other languages, such as Javascript.
Consider the code below
#include <iostream>
using std::cout;
using std::endl;
int main()
{auto func = [](int x)->int{ return x*x; };
int x = 11;
cout << func(x) << endl;
return 0;
}
[](int x)->int{ return x*x; }
defines the lambda function. Here []
is compiler specification, indicating the compiler that we are creating a lambda function. ()
is used to specify the argument list. In this case, this function takes one argument of type int
, hence we use (int x)
. ->int
indicates the return type. In this case the function returns an int
. { return x*x; }
is the function body. Here the lambda function is created and its pointer is assigned to variable func
. auto
is a keyword that can be used to define a variable. In this case the type of the variable is infered from context.
It is okay to miss ()
if the function takes no arguments.
It is okay to miss ->
if the compiler can discern the retunr type.
It is possible to call the function right away by using ()
at the end of the body as shown below
See the example below.
#include <iostream>
using std::cout;
using std::endl;
int main()
{int val = 11;
int x){ return x*x; }(val) << endl;
cout << [](
return 0;
}
It is possible to pass lambda functions as arguments where function pointers are expected. Of course the signature of the lamda function should match the signature of the argument, representing the function pointer.
#include <iostream>
using std::cout;
using std::endl;
int do_some_process(int x, int (*process)(int))
{return process(x);
}
int main()
{2, [](int x)->int{ return x*x;} ) << endl;
cout << do_some_process(return 0;
}
Consider the following program
#include <iostream>
using std::cout;
using std::endl;
int main()
{int val = 11;
return val*val; }() << endl;
cout << [&](){
return 0;
}
Notice that the lambda function [&](){ return val*val; }
is referring to a variable val
, which is not defined in this lambda function. The variable val
is also not passed into this lambda function. So how did this variable got here. We use the variable capture indicated by [&]
to capture the variable val
defined in the caller context to be available within the lambda function.
Notation | Effect |
---|---|
[] |
Capture nothing |
[&] |
Capture any variable referenced in the lambda function by reference |
[=] |
Capture any variable referenced in the lambda function by value (i.e., making a copy) |
[=,&foo] |
Capture any variable referenced in the lambda function by value (i.e., making a copy); capture variable foo by reference. |
[this] | Capture the this pointer of the enclosing class. This means that all members of the enclosing class are available within the lambda function |
[foo] | Capture variable foo by making a copy; do not capture anything else |
Use care since lambda function can modify the values of the variables that are captured by reference. Also take care when returning lambda function from a function, since captured variables might become invalid.
Below we see an example of lambda function being used with the Standard Template Library (STL)
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
int main()
{std::vector<int> v;
for (int i=0; i<10; ++i) v.push_back(i*2);
std::for_each(v.begin(), v.end(), [](int val){ cout << val << endl; });
return 0;
}