Вы находитесь на странице: 1из 11

[Linux Quality Database] [Articles]

Pointers to C++ Member Functions


Michael D. Crawford
GoingWare Inc.
crawford@goingware.com
Copyright 2002 Michael D. Crawford.
Permission is granted to copy, distribute and/or modify this document under the terms of
the GNU Free Documentation License, Version 1.1 or any later version published by the
Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and one
Back Cover Text: "This contains material from the Linux Quality Database at
http://linuxquality.sunsite.dk". A copy of the license is included in the document entitled
"GNU Free Documentation License".

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.

Contents

Abstract
Introduction
Member Function Pointers Are Not Just Simple Addresses
Caching the Outcome of a Decision
The Performance of Member Function Pointers
Details About Using Member Function Pointers
A Different Sort of Polymorphism

Introduction

I don'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
long
long
private:
long

TimesOne() const;
TimesTwo() const;
TimesThree() const;
mVal;

};
typedef long (Test::*Multiplier)() const;
int main( int
{
using
using
using

argc, char **argv )


std::cerr;
std::endl;
std::cout;

if ( argc != 3 ){
cerr << "Usage: PtrTest value factor"
return 1;
}
Multiplier funcPtr;

<< endl;

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

A Different Sort of Polymorphism


Polymorphism in C++ is usually regarded as always implemented in the form of class
heirarchies containing virtual member functions.
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.
[Linux Quality Database] [Articles] [Top]

Вам также может понравиться