Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Function pointers vs. Functors

evilrixEngineering Manager
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.
Published:
Updated:
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;
                      }

Open 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.
                      }

Open 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);
                      }

Open 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);
                      }

Open 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();
                      	}
                      };

Open 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;
                      	}
                      };

Open 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));
                      }

Open 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;
                      }

Open 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;
                      }

Open 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.
 
15
32,398 Views
evilrixEngineering Manager
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.

Comments (1)

Kevin CrossChief Technology Officer
CERTIFIED EXPERT
Most Valuable Expert 2011

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

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.