c++成员函数指针typedef

A tutorial on a useful yet poorly understood language feature,
useful to cache the outcome of a decision or to enable a different sort ofpolymorphism.

Mike Crawford

Mike Crawford
Consulting Software Engineer
[email protected]

Copyright © 2002, 2012, 2016 Michael D. Crawford.

Creative Commons License
This work is licensed under aCreative Commons Attribution-ShareAlike 2.5 License.

Abstract

Pointers to Member Functions are one of C++'s more rarelyused features, and are often not well understood even by experienceddevelopers. This is understandable, as their syntax is necessarilyrather clumsy and obscure.

While they do not have wide applicability, sometimes memberfunction pointers are useful to solve certain problems, and whenthey do apply they are often the perfect choice, both for improvedperformance and to make the code sensible. They work very well tocache the result of a frequently made decision, and to implement adifferent 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

Introduction

I don't have any hard numbers on how frequently member functionpointers are used. While Ido see others mention them sometimes in Usenet and mailing listposts, I have yet to find someoneelse use one in code I have worked with, so my impression is thatthey are not commonly applied.

Exceptional C++ Style cover

Exceptional C++ Style
40 New Engineering Puzzles, Programming Problems, and Solutions(C++ in Depth Series)

by Herb Sutter

[Buy]

Member function pointers are important because they provide anefficient way to cache the outcome of a decision over which memberfunction to call. They can save time, and in some cases, provide adesign alternative that avoids the need to implement such decisioncaching through memory allocation. I will return to this further on.

Member function pointers allow one to call one of several of anobject'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 functioncalled) directly by themselves. They must be called on behalf of someobject, that then provides the "this" pointer for use by the memberfunctions.

To illustrate how to declare and call a member function pointer,I will start by giving an example ofdeclaring and dereferencing an ordinary pointer to a non-memberfunction. You declare a functionpointer by giving the prototype of a function it can point to,with the name of the function replacedby (*pointerName). Regular function pointers sharethe 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-ofoperator & 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 prototypeof a function it can point to, as before, but the name of thisfunction is replaced by a construction that scopes the pointer -you give it the name of the class whose member functions it canpoint to, as (ClassName::*pointerName). Note thata given member function pointer can only point to functions thatare members of the class it was declared with. It cannot beapplied to an object of a different class even if it has memberfunctions with the same signature.

You dereference a member function pointer by using .*or ->*, supplying a reference or pointer to an objecton 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 ishard 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 AreNot Just Simple Addresses

Most C and C++ programmers know that it is bad style to assume that apointer 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 thesame size as each other. For example, in 16-bit x86 programming near pointersand far pointers may have different sizes, where the far pointers consist ofthe segment and offset together, while near pointers just have the offset.

Member function pointers are generally small structures, that encodeinformation about a function's virtualness, multiple inheritance and so on.

In the case of the example shown below, compiled with g++ 2.95.2 ona PowerPC G3 Mac OS X iBook, I found that the size of the member functionpointer I created was eight bytes.

This can result in surprises to the user. For example, MicrosoftVisual C++ 6 allows the programmer to make an optimization (which isapparently enabled by default) which can cause member function pointersthat are intended to be the same type but are declared in differentcircumstances to have different sizes. Using the wrong setting foryour project may result in an apparently gross code generation bug,because a member function pointer returned by a function that suppliesthem 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" thathas a choice between "best case always" and "most general always". Ifyou work with member function pointers in Visual C++, check thedocumentation for what these settings do and select the right one; ifin doubt, select "most general always".

Caching the Outcome of a Decision

One of the best uses for member function pointers is caching theoutcome of a decision over which ofseveral member functions should be called in a particular circumstance.If a decision is always going to yield the same result, then it may befaster and even cleaner to make the decision just once ahead of time,then store the outcome in the form of a member function pointer. Thisis especially advantageous when the decision will be made repeatedlyin a loop.

Here is an admittedly silly (but hopefully clear) example, thatshows a member function pointer being used to store the outcome of adecision. 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 couldbecause performs a switch decision many times inside a loop, always reaching the samedecision. It is a good candidate to refactor by using a pointer tomember function. Again it is a silly example but I wanted to bevery 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 thedecision is outside the loop, and the loop is repeated in eachbranch 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;
		//...
	}
}
Refactoring cover

Refactoring: Improving the Design of Existing Code

by Martin Fowler, Kent Beck, John Brant, William Opdyke and Don Roberts

[Buy]

If you want to avoid repeating the loop implementations and eachbranch of the decision has similar code, you can place them insidesubroutines.

Member function pointers are the best solution when it isnot practical to refactor this way. Onereason might be that the loop and the decision are in code thatbelongs to different classes, and you do not want to expose theimplementation of the class that makes the decision. Here is theMultiplyIt code above, refactored to use a pointerto 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 MemberFunction Pointers

Unfortunately, calling a member function by dereferencing a memberfunction is more complicated thansimply doing a subroutine jump off a register. The pointers areactually small structures and a little bit of work is required tofind the actual address of the subroutine to jump to.

I'm afraid I do not have the g++ source code at hand or I couldshow you the implementation. I know that in tracing through callsvia member function pointers in Metrowerks CodeWarrior for Windows, I found that a call wouldrun 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 ifit stays in the CPU's L1 cache, but it is not as fast as a simplecompare and conditional branch.

If the decision your code is making repeatedly is very quick torun, it may not be to your advantage to use a member function pointer.A simple if statement that compares two numeric values, orchecks the value of a bool, or possibly a switchstatement whose alternatives are all contained in a small range (so it iseasy for the compiler to build a jump table) may be quicker thandereferencing 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 apointer to a member function may be a big win.

Details About Using Member Function Pointers

You may understand the reasons for implementing pointers tomember functions as structures if you see that they can beassigned to the addresses of routines with different kinds ofimplementations, 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 aninline function, but this is perfectly normal. If you do this,the compiler will place a normal subroutine version of theinline's implementation in an object file and give you theaddress of that, so the function pointer does not reallypoint to an inline function at all.)

Although a static member function may appearto have the same calling convention, it really does notbecause it is not passed the this pointer -this is passed to your member functions justlike any other parameter, but it is not given explicitlyin the member function's prototype. You cannot use pointersto 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 avirtual member function directly - the type whose member functiongets called is the dynamic type of the object it is called onbehalf 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 inthe form of class heirarchies containing virtual member functions.

Design Patterns cover

Design Patterns

by Erich Gamma, Richard Helm, Ralph Johnson andJohn Vlissides

[Buy]

An object of a derived class can be supplied to create a pointeror reference to what is apparently the base class; a function pointerlookup in the vtbl is done when calling a virtual memberfunction off a pointer or reference, so that the function called willbe based on the dynamic type that the pointer or reference denotes -that is, it will be from the actual type of the object that wasallocated, rather than the static type that the base class pointeror reference is declared as.

However, the concept of polymorphism can take a more generalmeaning than that, and I have seen mailing list postings advocatingthat it should also include the use of templates that allow sourcecode with identical syntax to be applied to objects of unrelatedtypes. This std::vector can be regarded as a polymorphiccontainer that is parameterized by the type supplied as a parameterwhen a vector object is declared.

Pointers to member functions can be used to implement a differentkind of polymorphism. In the regular type, we determine which memberfunction ultimately gets called by allocating objects of differenttypes, that are related members in an inheritance tree. This isimplemented by having the vptr that is a hiddenmember of the object point at the appropriate vtbl.

In this other form you create objects that are always of the sametype, but determine which member function gets called by choosingwhich member function's address gets assigned to a member functionpointer. One interesting advantage is that you can change thebehaviour of an object during its lifetime without having to allocatea new one of a different type as you would with the regular sort ofinheritance-based polymorphism.


猜你喜欢

转载自blog.csdn.net/liweigao01/article/details/80579212