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);
= square;
fnptr
<< fnptr(2) << endl;
cout
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;
= *a;
tmp *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
= swap; m
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
=gt; l
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) {
+= a[k];
sum }
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
= sum; n
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;
<< do_some_process(square, x) << endl;
cout << do_some_process(neg, x) << endl;
cout
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
<< a(3) << endl;
cout
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
<< do_some_process(a, 2) << endl;
cout << do_some_process(b, 2) << endl;
cout
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 bint x = 9;
<< do_some_process(a, x) << endl;
cout << do_some_process(b, x) << endl;
cout << do_some_process(timestwo, x) << endl;
cout
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;
<< func(x) << endl;
cout
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()
{
<< do_some_process(2, [](int x)->int{ return x*x;} ) << endl;
cout 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.
#include <iostream>
using std::cout;
using std::endl;
class Disk
{
protected:
double _radius;
const double _pi;
public:
()
Disk: _radius(10), _pi(3.14159)
{}
double area() {
return
// Note that the following is a lambda function.
//
// The return type isn't specificed, since it can be determined by the
// compiler from context. (it is a double)
//
// The function doesn't take any arguments either. Note the empty
// () that tell us that it is a function call.
//
// Through [this] the lambda function captures the this pointer
// for the current instance of Disk class. Using this it is then
// able to access the private members (data) and methods (functions).
[this]{ return this->_pi * this->_radius * this->_radius; }();
};
};
int main()
{
int x = 1, y = 2, z = 3;
<< "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
cout
// Capture nothing []
// x, y defined in the main function are not available
// in the lambda function. The only way to pass this
// information is via function arguments.
<< "Capture nothing = " << [](int a, int b)->int{ return a+b; }(x, y) << endl;
cout
// Capture by reference [&]
// Note that we never pass x and y defined in the main
// function to the lambda function; however,
// these values are available within the lambda function.
// Furthermore changes made to x within the lambda function
// is reflected in main function
<< "Before changing x within the lambda function [&]\nx = " << x << endl;
cout << "Capture by reference = " << [&]()->int{ x=5; return x+y; }() << endl;
cout << "After changing x within the lambda function [&]\nx = " << x << endl;
cout
// Capture by value [=]
// Note that we never pass x and y defined in the main
// function to the lambda function; however,
// these values are available within the lambda function.
// Changes made to x within the lambda function
// are not reflected in main function
<< "Capture by value = " << [=]()->int{
cout /* this statement gives a compile error x=10;*/
return x+y; }() << endl;
// Capture by value variable x in the main [x]
<< "Capture by value only x = " << [x]()->int{
cout /* this statement gives a compile error, because y isn't available
within the lambda function return x+y;*/
return x; }() << endl;
// Capture by value variable x in the main and capture by
// reference valriable y in the main [x,&y]
<< "Before changing y within the lambda function [x,&y]\ny = " << y << endl;
cout << "Capture by value only y = " << [x,&y]()->int{
cout /* this statement gives a compile error, because z isn't available
within the lambda function return x+y+z;*/
= 10;
y return x+y; }() << endl;
<< "After changing y within the lambda function [x,&y]\ny = " << y << endl;
cout
;
Disk d<< "Area of a disk = " << d.area() << endl;
cout
return 0;
}
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;
}