Function pointers vs. Functors

AID: 849
  • Status: Published

11050 points

  • Byevilrix
  • TypeTutorial
  • Posted on2009-06-07 at 10:04:18
Awards
  • Community Pick
  • Experts Exchange Approved
Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, what should it do now that it has this tag? The simplest way to handle this would be to invoke a Callback function that knows how to handle tags. This is exactly how SAX (Simple API for XML) style XML parsers work. There are, of course, other reasons for invoking a callback function: implementing logging, enumerating windows and many more. All these are examples of event driven programming; when you encounter an event and you call an event handler to handle it.

In C, if you want to provide a callback mechanism you must implement a callback function and then pass the address of the callback function to the invoker (the code that will call your callback when it's needed). Unfortunately, C style function pointers have a number of drawbacks:

1. A function contains no instance state, mainly because there is no such thing as multiple instances of a function; there will only ever be one instance and that is global. Sure, it is possible to declare a local static within the function that can retain state between calls, but since a function only has one instance there can only be once instance of the static member and that must be shared between function calls.

A function with local static state

MyDataClass & foo()
{
	static MyDataClass data;
	// Do some work
	return data;
}
                                    
1:
2:
3:
4:
5:
6:

Select allOpen in new window



2. If you try to maintain state in the function by using a local static variable the function will not be reentrent, so it cannot be safely called on multiple threads without the additional overhead of thread synchronisation to ensure access to the local static data has mutual exclusion semantics. This effectively means the function can only allow one thread into it at any one time and that will create quite a bottle neck of thread contention (multiple threads all fighting for access to a single resource). Furthermore, if access is required to the static state local variable after the function has finished the caller must continue to block access until the state has either been copied for use or it is no longer required otherwise it'll be read by one thread whilst another is potentially trying to modify it, resulting in a race condition.

A function with local static state using a mutex to attempt to make the function reentrant

MyDataClass & foo(Mutex m)
{
	ScopedLock sl(m); // Ensure no other thread can get in here

	static MyDataClass data;
	// Do some work
	return data;

	// Note once the scoped lock ends here another thread could enter
	// and modify data before the caller has a chance to copy it
	// so even this isn't a very good solution, really the mutex should
	// be locked by the caller.
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Select allOpen in new window



3. Function pointers do not work very well with templates, if only one signature of the function exists then things will work otherwise the compiler will complain of template instantiation ambiguities that can only be resolved through ugly function pointer casting.

A function pointer casting to resolve template instantiation ambiguities

void callback_func(void *){}
void callback_func(char *){}

template <typename funcT>
void consumer(funcT callback_func)
{}

int main()
{
	consumer(callback_func); // ERROR: Which one?

	consumer((void (*)(void*))callback_func);
	consumer((void (*)(char*))callback_func);
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:

Select allOpen in new window



4. What if your invoker expects a function that takes only one parameter and you want to use a 3rd party function as the callback, to which you have no source code, and this has a completely different signature to that expected by the invoker? Well, you can wrap the function with another function that adapts the interface but this wrapper will need to have the additional required parameters hard coded into it. What if you want the flexibility of changing what the parameter values are for arbitrary calls to the invoker? Well, for that you will need to write an adaptor for each and every permutation and even then it's still fixed at compile time so it's not easily extensible and becomes quite messy with multiple functions that need to be maintain.

An adaptor using a function

bool third_party_func(int, char, float){ return true; }

template <typename funcT>
void invoker(funcT callback_func)
{
	callback_func(int());
}

// C style adaptors
void adaptor_func1(int n)
{
	third_party_func(n, 'c', 1.1f); // Hard coded bindings, cannot be changed at runtime
}

void adaptor_func2(int n)
{
	third_party_func(n, 'b', 5.9f); // Hard coded bindings, cannot be changed at runtime
}

int main()
{
	// C style function has hard coded bindings
	invoker(adaptor_func1);
	invoker(adaptor_func2);
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:

Select allOpen in new window



So, is there a better way? Well, now you come to mention it, yes there is! Enter the functor.

What is a functor? Well, simply put it is a function object, and in C-PlusPlus we model a functor using a normal class object. What makes the object a functor is the provision of a function operator, which gives the class object function semantics. The function operator, in its simply canonical for looks like this...

The basic canonical form of a functor

class Functor
{
public:
	R operator()(T1, ..., Tn)
	{
		return R();
	}
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:

Select allOpen in new window



Where R is the return type, T is a parameter type and just like any function the number of parameters is arbitrary. So a more concrete type of a functor that takes 2 int parameters and returns a bool would be as follows...

A concrete example of a simple functor

class LessThanFunctor
{
public:
	bool operator()(int lhs, int rhs)
	{
		return lhs < rhs;
	}
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:

Select allOpen in new window



It's pretty clear to see that this simple functor will compare two integers and if the left-hand-side is less than the right-hand-side it will return true, else it will return false.

How do we use a functor and how does it differ from a function? Well, as already stated a functor is just a class with function semantics, of course it is still just a class and like all classes it can contain data and function members and instances can be created. What this means is that each instance of the functor object can contain and maintain its own internal state. This state can either be set during construction of the function or after construction. This means the functor can be primed with state before use and it can set its own state during use, which can be extracted after.

Lets look at a functor in action. First of all, let's revisit the issue of binding parameters to 3rd party functions to facilitate using them in with an invoker that expects a different signature. With a functor the additional parameters can be loaded into the functor instance when it is created, which can then be bound to the third party call at run-time and not compile time. In fact this is exactly what the standard library functions bind1st and bind2nd do. Look at the example below, notice how much neater the solution is, no need for multiple functions to provide multiple bindings and also note that the bindings provided are passed into the constructor of the functor rather than being hard-coded into it, thus allowing these bindings to be changed at run-time.

An adaptor using a functor

bool third_party_func(int, char, float){ return true; }

template <typename funcT>
void invoker(funcT callback_func)
{
	callback_func(int());
}


// C++ style adaptor
class adaptor_functor
{
public:
	// Initialize runtime bindings
	adaptor_functor(char cb, float fb) : cb_(cb), fb_(fb){}
	
	void operator()(int n)
	{
		third_party_func(cb_, fb_, n);
	}
	
private:
	char cb_;
	float fb_;
};

int main()
{

	// C++ functor has bindings that can be set ar runtime via the functors constructor
	invoker(adaptor_functor('a', 2.3f));
	invoker(adaptor_functor('z', 0.0f));
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:

Select allOpen in new window



How about another example? A common usage of a callback function is to provide a user defined logging mechanism for a 3rd party library. The library will callback to your logging callback function, provide it with some details and it is up to you to design a function that will do something useful with those details, like write them to a log file. Furthermore we must record how many times we logged something and how many of these were errors so at the end of the call to the third-party library we can append a count of entries to the log file. Implementing this using a standard C function callback mechanism would require quite a lot of effort, however, using a functor it's pretty simple.

A logging functor in action

#include <iostream>
#include <sstream>
#include <string>
#include <ctime>

// Note that this logging class assumes single threading, additional code would be required
// to provide mutual exclusion semantics, which are outside the scope of this article
class LoggingFunctor
{
public:

	// Constructor allows user defined output stream
	LoggingFunctor(std::ostream & os) :
		os_(os), nErrCnt_(0), nLogCnt_(0) {}
	

	// Overload for std::string
	void operator()(std::string const & s, bool bErr)
	{
		// Hand off to overload for char const *
		(*this)(s.c_str(), bErr);
	}
	
	// The main logging funcion
	void operator()(char const * szMsg, bool bErr)
	{
		// Count log item
		++ nLogCnt_;
		
		// Display date & time
		time_t t = time(0);
		char tbuf[80];
		strftime (tbuf,80,"%x %X ",localtime(&t));
		os_ << tbuf;
		
		// Is this an error message?
		if(bErr)
		{
			// Count error and display error prefix
			++ nErrCnt_;
			os_ << "ERROR: ";
		}
		
		// NOw log it
		os_ << szMsg << std::endl;
	}
	
	// Accessors to the log and error count
	int GetErrCnt() const { return nErrCnt_; }
	int GetLogCnt() const { return nLogCnt_; }
	
private:
	// Non-copyable semantics to prevent accidental copy or assignment
	LoggingFunctor(LoggingFunctor const &);
	LoggingFunctor operator=(LoggingFunctor const &);
	
private:
	std::ostream & os_;
	int nErrCnt_;
	int nLogCnt_;
};


template <typename LoggingFunctorT>
void Mock3rdPartyCall(LoggingFunctorT & logger)
{
	for(int i = 0 ; i < 25; ++i)
	{
		// Build a log message
		std::stringstream ss;
		ss << "Log entry " << i;
		
		// Log it, treat every 3rd iteration as an error
		logger(ss.str(), i%3 == 0);
	}
}

int main()
{
	// Log to stdout for this example
	LoggingFunctor loggingFunctor(std::cout);
	
	// Call the mock 3rd party function
	Mock3rdPartyCall(loggingFunctor);
	
	std::cout
	 	<< std::endl
		<< loggingFunctor.GetLogCnt() << " items logged, "
		<< loggingFunctor.GetErrCnt() << " of which were errors." << std::endl;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:

Select allOpen in new window



Okay, we've had a couple of concrete example of using functors but I can hear you screaming, "are there any drawbacks?". Well, yes. For a start functors are not C API friendly and cannot really be made to work with an existing function that already expects an old C style function pointer. Other drawbacks? Well, unlike functions, functors have to be instantiated and like any object a functor can throw on construction so additional consideration must be given to ensure code is exception safe. Other than these few issues the use of a C-PlusPlus functor has few downsides and many benefits.

So how do you start making use of functors? When writing C-PlusPlus code that uses callbacks it is always a good idea to implement support for functors as well as function pointers. This is exactly what the Standard Template Library does. The code to support both functors and function pointers is quite simple, requiring only the use of a simple template parameter rather than an explicit function pointer type. Since the calling semantics of a functor and a function are identical, the invoker works just as well with either. In template meta-programming parlance we say that a functor models a function concept and, therefore, either a function or a functor can be passed as a template parameter where that parameter represents a function concept. Let's go back to the LessThan functor to see this.

How to write an invoker to use either a functor or a function

#include <iostream>

class LessThanFunctor
{
public:
	bool operator()(int lhs, int rhs)
	{
		return lhs < rhs;
	}
};

bool LessThanFunction(int lhs, int rhs)
{
	return lhs < rhs;
}

// To make this work with a function or functor we just use a template parameter
template <typename functorT>
bool Invoker(functorT func, int x, int y)
{
	return func(x,y);
}

int main()
{
	std::cout
		<< "Functor:  " << Invoker(LessThanFunctor(), 5, 6)
		<< std::endl
		<< "Function: " << Invoker(LessThanFunction, 5, 6)
		<< std::endl
		<< "Functor:  " << Invoker(LessThanFunctor(), 7, 6)
		<< std::endl
		<< "Function: " << Invoker(LessThanFunction, 7, 6)
		<< std::endl;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:

Select allOpen in new window



You'll find the functor concept is used ubiquitously by the C-PlusPlus STL (Standard Template Library) as well as the Boost libraries. Knowing how to write and use functors is a key success factor in writing generic and reusable code and being able to make use of advanced features of the STL and Boost. They are a tool that should be in any C-PlusPlus programmers toolkit!

Further reading: The Function Pointer Tutorials - Introduction to the basics of C and C-PlusPlus Function Pointers, Callbacks and Functors.
 
Asked On
2009-06-07 at 10:04:18ID849
Tags

C++

Topic

C++ Programming Language

Views
14045

Comments

Expert Comment

by: mwvisa1 on 2010-08-11 at 19:42:07ID: 18068

Nice article, evilrix! You have my yes vote above ...

Add your Comment

Please Sign up or Log in to comment on this article.

Join Experts Exchange Today

Gain Access to all our Tech Resources

Get personalized answers

Ask unlimited questions

Access Proven Solutions

Search 3.2 million solutions

Read In-Depth How-To Guides

1000+ articles, demos, & tips

Watch Step by Step Tutorials

Learn direct from top tech pros

And Much More!

Your complete tech resource

See Plans and Pricing

30-day free trial. Register in 60 seconds.

Loading Advertisement...

Top C++ Experts

  1. jkr

    261,219

    Guru

    1,000 points yesterday

    Profile
    Rank: Savant
  2. sarabande

    121,084

    Master

    3,800 points yesterday

    Profile
    Rank: Sage
  3. Infinity08

    54,855

    Master

    0 points yesterday

    Profile
    Rank: Genius
  4. ambience

    50,164

    Master

    0 points yesterday

    Profile
    Rank: Sage
  5. Zoppo

    48,382

    0 points yesterday

    Profile
    Rank: Genius
  6. evilrix

    48,358

    80 points yesterday

    Profile
    Rank: Genius
  7. satsumo

    22,400

    0 points yesterday

    Profile
    Rank: Guru
  8. tampnic

    19,040

    0 points yesterday

    Profile
    Rank: Master
  9. phoffric

    16,596

    0 points yesterday

    Profile
    Rank: Genius
  10. DanRollins

    16,330

    0 points yesterday

    Profile
    Rank: Genius
  11. duncan_roe

    14,400

    0 points yesterday

    Profile
    Rank: Genius
  12. gtokas

    12,700

    0 points yesterday

    Profile
    Rank: Wizard
  13. AndyAinscow

    12,132

    0 points yesterday

    Profile
    Rank: Genius
  14. mccarl

    9,600

    0 points yesterday

    Profile
    Rank: Wizard
  15. TommySzalapski

    8,800

    0 points yesterday

    Profile
    Rank: Genius
  16. pepr

    7,824

    0 points yesterday

    Profile
    Rank: Genius
  17. kaufmed

    7,168

    0 points yesterday

    Profile
    Rank: Genius
  18. Thommy

    6,700

    0 points yesterday

    Profile
    Rank: Wizard
  19. ubound

    6,550

    0 points yesterday

    Profile
    Rank: Master
  20. kuroji

    6,000

    0 points yesterday

    Profile
  21. for_yan

    6,000

    0 points yesterday

    Profile
    Rank: Genius
  22. mrwad99

    4,600

    0 points yesterday

    Profile
    Rank: Wizard
  23. masheik

    4,572

    0 points yesterday

    Profile
    Rank: Guru
  24. Orcbighter

    4,332

    0 points yesterday

    Profile
    Rank: Master
  25. ozo

    4,300

    0 points yesterday

    Profile
    Rank: Savant

Hall Of Fame