 |
Pointers to C++ Member Functions
|
by: Michael
D. Crawford
Abstract
Pointers to Member Functions are one of C++'s more rarely used
features, and are often not well understood even by experienced
developers. This is understandable, as their syntax is necessarily
rather clumsy and obscure.
While they do not have wide applicability, sometimes member
function pointers are useful to solve certain problems, and
when they do apply they are often the perfect choice, both
for improved performance and to make the code sensible. They
work very well to cache the result of a frequently made decision,
and to implement a different sort of polymorphism.
I discuss what member function pointers are, how to declare
and use them, and give some examples of problems that they
solve very well.
Introduction
Idon't have any hard numbers on how frequently member
function pointers are used. While I do see others mention them
sometimes in Usenet and mailing list posts, I have yet to find
someone else use one in code I have worked with, so my impression
is that they are not commonly applied.
Member function pointers are important because they provide
an efficient way to cache the outcome of a decision over which
member function to call. They can save time, and in some cases,
provide a design alternative that avoids the need to implement
such decision caching through memory allocation. I will return
to this further on.
Member function pointers allow one to call
one of several of an object's member functions indirectly.
Each of the functions
whose "address" is stored must share the same signature.
I put "address" in quotes because
the information stored in a member function pointer is not
simply the memory
address of the start of the member function's code; conceptually
it is an offset into the list of functions declared by the
class, and in the case of virtual functions will include a
real offset into the vtbl, or table of virtual function pointers.
Member function pointers cannot be dereferenced
(have their function called) directly by themselves. They
must be called
on behalf of some object, that then provides the "this" pointer
for use by the member functions.
To illustrate how to declare and call a member function pointer,
I will start by giving an example of declaring and dereferencing
an ordinary pointer to a non-member function. You declare a
function pointer by giving the prototype of a function it can
point to, with the name of the function replaced by (*pointerName).
Regular function pointers share the same syntax between C and
C++:
void Foo( int anInt, double aDouble );
void Bar()
{
void (*funcPtr)( int, double ) = &Foo;
(*funcPtr)(
1, 2.0 );
}
For regular function pointers, it is optional to use the
address-of operator & when taking the address of a function, but it
is required for taking the address of member functions. g++
will compile source that leaves it out, but emits a warning.
To declare a pointer to member function, you give the prototype
of a function it can point to, as before, but the name of this
function is replaced by a construction that scopes the pointer
- you give it the name of the class whose member functions
it can point to, as (ClassName::*pointerName). Note that a
given member function pointer can only point to functions that
are members of the class it was declared with. It cannot be
applied to an object of a different class even if it has member
functions with the same signature.
You dereference a member function pointer by
using .* or ->*,
supplying a reference or pointer to an object on the left,
as appropriate, and the function pointer on the right.
Here is a simple example:
class Foo
{
public:
double One( long inVal );
double Two( long inVal );
};
void main( int argc, char **argv )
{
double (Foo::*funcPtr)( long ) = &Foo::One;
Foo aFoo;
double result =(aFoo.*funcPtr)( 2 );
return 0;
}
Declaring a member function pointer is clumsy at best and
is hard to get right until you have used them for a
while. Rather than declaring them using the
full prototype each time, it is helpful to use a typedef as I show in the
example below.
Member Function Pointers Are Not Just Simple Addresses
Most C and C++ programmers know that it is bad style to assume
that a pointer is the same size as an int, although this
may often be the case. What is less well known is that pointers
of different types may not be the same size as each other.
For example, in 16-bit x86 programming near pointers and
far pointers may have different sizes, where the far pointers
consist of the segment and offset together, while near pointers
just have the offset. Member function pointers are generally
small structures, that encode information about a function's
virtualness, multiple inheritance and so on.
In the case of the example shown below, compiled with g++
2.95.2 on a PowerPC G3 Mac OS X iBook, I found that the size
of the member function pointer I created was eight bytes.
This can result in surprises to the user. For example, Microsoft
Visual C++ 6 allows the programmer to make an optimization
(which is apparently enabled by default) which can cause member
function pointers that are intended to be the same type but
are declared in different circumstances to have different sizes.
Using the wrong setting for your project may result in an apparently
gross code generation bug, because a member function pointer
returned by a function that supplies them may have a different
size than the recipient function expects, causing bogus data
to be overwritten on the stack.
There is an item in VC++'s settings labeled "representation" that
has a choice between "best case always" and "most
general always". If you work with member function pointers
in Visual C++, check the documentation for what these settings
do and select the right one; if in doubt, select "most
general always".
Caching the Outcome of a Decision
One of the best uses for member function pointers is caching
the outcome of a decision over which of several member functions
should be called in a particular circumstance. If a decision
is always going to yield the same result, then it may be
faster and even cleaner to make the decision just once ahead
of time, then store the outcome in the form of a member function
pointer. This is especially advantageous when the decision
will be made repeatedly in a loop.
Here is an admittedly silly (but hopefully clear) example,
that shows a member function pointer being used to store the
outcome of a decision. It also illustrates the use of typedef:
#include <stdlib.h>
#include <iostream>
class Test
{
public:
Test( long inVal )
: mVal( inVal ){}
long TimesOne() const;
long TimesTwo() const;
long TimesThree() const;
private:
long mVal;
};
typedef long (Test::*Multiplier)() const;
int main( int argc, char **argv )
{
using std::cerr;
using std::endl;
using std::cout;
if ( argc != 3 ){
cerr << "Usage: PtrTest value factor" << endl;
return 1;
}
Multiplier funcPtr;
switch( atol( argv[ 2 ] ) ){
case 1:
funcPtr = &Test::TimesOne;
break;
case 2:
funcPtr = &Test::TimesTwo;
break;
case 3:
funcPtr = &Test::TimesThree;
break;
default:
cerr << "PtrTest: factor must range from 1 to 3" << endl;
return 1;
}
cout << "sizeof( funcPtr )=" << sizeof(
funcPtr ) << endl;
Test myTest( atol( argv[ 1 ] ) );
cout << "result=" << (myTest.*funcPtr)() <<endl;
return 0;
}
long Test::TimesOne() const
{
return mVal;
}
long Test::TimesTwo() const
{
return 2 * mVal;
}
long Test::TimesThree() const
{
return 3 * mVal;
}
Now I present an example that does not perform as well
as it could because performs a switch decision many
times inside a loop, always reaching the same
decision. It is a good candidate to refactor by using a pointer to member
function. Again it is a silly example but I wanted
to be very clear:
#include <exception>
class Test
{
public:
Test( long inFactor )
: mFactor( inFactor ){}
long TimesOne( long inToMultiply ) const;
long TimesTwo( long inToMultiply ) const;
long TimesThree( long inToMultiply ) const;
long MultiplyIt( long inToMultiply ) const;
private:
long mFactor;
};
long Test::MultiplyIt( long inToMultiply ) const
{
switch( mFactor ){ // decision made repeatedly that always yields the same
result
case 1:
return TimesOne( inToMultiply );
break;
case 2:
return TimesTwo( inToMultiply );
break;
case 3:
return TimesThree( inToMultiply );
break;
default:
throw std::exception();
}
}
void MultiplyThem( long inFactor )
{
Test myTest( 2 );
long product;
// Call a function that makes the same decision many times
for ( long i = 0; i < 1000000; ++i )
product = myTest.MultiplyIt( i );
}
In most cases where an identical decision is made inside
a loop, it is better to refactor the code so that the
decision is outside the loop, and the loop
is repeated in each branch of the loop (or packaged inside a subroutine):
void Foo( long value )
{
for ( long i = 0; i < 1000000; ++i ){
switch( value ){ // BAD CODE: always reaches the same decision
case 1:
//...
break;
case 2:
//...
break;
case 3:
//...
break;
}
}
}
Instead we place the switch outside the loop:
void Foo( long value )
{
switch( value ){ // BETTER CODE: decision made only once
case 1:
for ( long i = 0; i < 1000000; ++i ){
//...
}
break;
case 2:
for ( long i = 0; i < 1000000; ++i ){
//...
}
break;
//...
}
}
If
you want to avoid repeating the loop implementations and
each branch of the decision has similar code, you can place
them inside subroutines.
Member function pointers are the best solution when it is
not practical to refactor this way. One reason might be that
the loop and the decision are in code that belongs to different
classes, and you do not want to expose the implementation of
the class that makes the decision. Here is the MultiplyIt code
above, refactored to use a pointer to member function:
#include <exception>
class Test
{
public:
Test( long inFactor );
long TimesOne( long inToMultiply ) const;
long TimesTwo( long inToMultiply ) const;
long TimesThree( long inToMultiply ) const;
long MultiplyIt( long inToMultiply ) const;
private:
typedef long (Test::*Multiplier)(
long inToMultiply ) const;
long mFactor;
Multiplier mMultFuncPtr;
static Multiplier GetFunctionPointer( long inFactor );
};
Test::Test( long inFactor )
: mFactor( inFactor ),
mMultFuncPtr( GetFunctionPointer( mFactor ) )
{
return;
}
Test::Multiplier Test::GetFunctionPointer( long inFactor )
{
switch ( inFactor ){ // Decision only made once!
case 1:
return &Test::TimesOne;
break;
case 2:
return &Test::TimesTwo;
break;
case 3:
return &Test::TimesThree;
break;
default:
throw std::exception();
}
}
long Test::MultiplyIt( long inToMultiply ) const
{
// Using cached decision result
return (this->*mMultFuncPtr)( inToMultiply
);
}
void MultiplyThem( long inFactor )
{
Test myTest( 2 );
long product;
for ( long i = 0; i < 1000000; ++i )
product = myTest.MultiplyIt( i );
}
The Performance
of Member Function Pointers
Unfortunately, calling a member function by dereferencing
a member function is more complicated than simply doing
a subroutine jump off a register. The
pointers are actually small structures and a little bit of work is required
to find the actual address of the subroutine to jump to.
Help Spread the Word!
Please encourage others to read this article by linking to
it from your website, your weblog, or from message boards.
I'm afraid I do not have the g++ source code at hand or I could
show you the implementation. I know that in tracing through
calls via member function pointers in Metrowerks CodeWarrior
for Windows, I found that a call would run a small piece of
assembly code provided by CodeWarrior's library. This is pretty
fast code, and will run very fast in a tight loop if it stays
in the CPU's L1 cache, but it is not as fast as a simple compare
and conditional branch.
If the decision your code is making repeatedly is very quick
to run, it may not be to your advantage to use a member function
pointer. A simple if statement that compares two numeric values,
or checks the value of a bool, or possibly a switch statement
whose alternatives are all contained in a small range (so it
is easy for the compiler to build a jump table) may be quicker
than dereferencing a member function pointer.
However, if the decision is complicated or lengthy to arrive
at, like string comparison or searching some data structure,
then using a pointer to member function may be a big win.
Details About Using Member Function Pointers
You may understand the reasons for implementing pointers to
member functions as structures if you see that they can be
assigned to the addresses of routines with different kinds
of implementations, as long as they have the same calling
convention:
class Different
{
public:
inline void InlineMember();
virtual void VirtualMember();
void OrdinaryMember();
static void StaticMember();
typedef void (Different::*FuncPtr)();
};
void Test()
{
Different::FuncPtr ptr = &Different::InlineMember;
ptr = &Different::VirtualMember;
ptr = &Different::OrdinaryMember;
}
(You may
be surprised to see me creating a pointer to an inline function,
but this is perfectly normal. If you do this, the
compiler will place a normal subroutine version of the inline's
implementation in an object file and give you the address
of that, so the function pointer does not really point to
an inline
function at all.)
However, although a static member function may appear to have
the same calling convention, it really does not because it
is not passed the this pointer - this is passed to your member
functions just like any other parameter, but it is not given
explicitly in the member function's prototype. You cannot use
pointers to member functions to store the address of a static
function (use an ordinary, non-member function pointer for
that):
void Fails()
{
Different::FuncPtr ptr = &Different::StaticMember;
}
mike% c++ different.cpp
different.cpp: In function `void Fails()':
different.cpp:24: initialization to `void (Different::*)()'
from `void (*)()'
Pointers to virtual member functions work just like calling
a virtual member function directly - the type whose member
function gets called is the dynamic type of the object it
is called on behalf of, not the static type of the
member function
pointer:
#include <iostream>
class Base
{
public:
virtual void WhoAmI() const;
typedef
void (Base::*WhoPtr)() const;
};
class Derived: public Base
{
public:
virtual void WhoAmI() const;
};
void Base::WhoAmI() const
{
std::cout << "I am the Base" << std::endl;
}
void Derived::WhoAmI() const
{
std::cout << "I am the Derived" << std::endl;
}
int main( int argc, char **argv )
{
Base::WhoPtr func = &Base::WhoAmI;
Base theBase;
(theBase.*func)();
Derived theDerived;
(theDerived.*func)();
return 0;
}
Running
the above program yields the following output:
mike% ./virtual
I am the Base
I am the Derived
An object of a derived class can be supplied to create a pointer
or reference to what is apparently the base class; a function
pointer lookup in the vtbl is done when calling a virtual member
function off a pointer or reference, so that the function called
will be based on the dynamic type that the pointer or reference
denotes - that is, it will be from the actual type of the object
that was allocated, rather than the static type that the base
class pointer or reference is declared as.
However, the concept of polymorphism can take a more general
meaning than that, and I have seen mailing list postings advocating
that it should also include the use of templates that allow
source code with identical syntax to be applied to objects
of unrelated types. This std::vector can be regarded as a polymorphic
container that is parameterized by the type supplied as a parameter
when a vector object is declared.
Pointers to member functions can be used to implement a different
kind of polymorphism. In the regular type, we determine which
member function ultimately gets called by allocating objects
of different types, that are related members in an inheritance
tree. This is implemented by having the vptr that is a hidden
member of the object point at the appropriate vtbl.
In this other form you create objects that are always of the
same type, but determine which member function gets called
by choosing which member function's address gets assigned to
a member function pointer. One interesting advantage is that
you can change the behaviour of an object during its lifetime
without having to allocate a new one of a different type as
you would with the regular sort of inheritance-based polymorphism.
About
The Author
Michael
D. Crawford is
the president of GoingWare.
This tutorial is licensed under a Creative
Commons Attribution-ShareAlike 2.5 License.
Website Magazine is a free magazine offering practical advice and helpful tools from industry experts to help any website achieve Internet success. Until now, there has not been a magazine that caters exclusively to the business of running a website. Website Magazine has tapped premier talent in the Internet industry for our content and each and every issue will contain practical advice and insights for website owners. Subscribe Free!
|
|