SkillAgentSearch skills...

Ptmf

How C++ pointer to member function works

Install / Use

/learn @minlux/Ptmf
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

C++ Pointer to Member Function

A challenge for every C++ programmer are pointer to member functions (ptmf). ptmfs are diffent than their C counterparts - function pointers (fp). Using function pointers in C is a common practice and gives the programmer a powerful technique for a good pice of software. When used correctly, fps can help to:

  • improve software's performance
  • lead to modulare and reusable components

Function pointers can also be used in C++. However fps can only be used to point to static class functions. fps can't be used to point to member functions, as a member function must be always called in combination with a object instance.

Example

Using a function pointer to point to char * Person::race(void) is possible, as this is a static function, that can be called without an instance of Person (race of any person is "human" - therfore no Person instance is neccessary).

char * (*getRace)(void) = &Person::race;

However a function pointer can't be used to point to char * Person::first_name(void) or char * Person::first_last(void) as a Person instance is required to get the name of the respective persion. This is where a C++ pointer to member function ptmf comes into play.

See also my notes in lambda.md to see how fps and ptmfs are used in combination with C++ lambdas.

Comparison fps and ptmfs

fps and ptmfs are diffrent, as they are ma for different techniques/languages. fps are made for C, ptmf are made for C++.

fps

  • A fp stores the address of a function
  • The fps type defines the function signature (of the fp)

To make life easier it is recommended to use typedefs, to define an explicit type for the fp: Detailed:

typedef char * (*GetRace_t)(void); //typedef
GetRace_t getRace; //definition
getRace = &Person::race; //assignmet
char * race = getRace(); //invocation

However, that is not mandatory, you can also do it like this: Brief:

char * (*getRace)(void) = &Person::race; //defintion (without an explicit typedef) and assignment

ptmfs

  • A ptmf stores either the address of a non-virtual function or a vtable offset to a virtual function
  • A ptmf stores the rules to modify the instance pointer (for the implicit type conversion required when inheritance comes into play)
  • A ptmf stores the information whether the ptmf points to a virtual or a non-virtual function
  • A ptmfs type defines the function signature (of the ptmf)
  • A ptmfs type defines the class they ptmf is related to

To make life easier it is recommended to use typedefs, to define an explicit type for the ptmf: Detailed:

typedef char * (Person::*GetName_t)(void); //typedef (with class specifier!)
GetName_t getName; //definition
getName = &Person::last_name; //assinment
char * name = (manuel->*getName)(); //invoation (in combination with *Person* instance pointer "manuel")

However, that is not mandatory, you can also do it like this: Brief:

char * (Person::*getName)(void) = &Person::last_name; //defintion (without an explicit typedef) and assignment

The syntax to invoce a ptmf is:

  • .* for the case the left hand side is an object reference
  • ->* for the case the left hand side is an object pointer

The parentheses around obj.*ptmf resp. obj->*ptmf are mandatory. E.g. (manuel->*getName)(); works, but manuel->*getName(); fails!

How ptmfs work

To figure out, how ptmfs work, lets introduce the following example:

Classes

We have four classes: Class PtmfX uses (multiple) inheritance to derive from class PtmfA, PtmfB and PtmfC. So PtmfX is composed out of the data attribues of PtmfA, PtmfB, PtmfC and its own data. This can be illustrated like this:

PtmfX

PtmfX starts with the data of PtmfC, followed by PtmfA, follwoed by PtmfB and finally by its own data. So the PtmfC data has an offset of PtmfXs this + 0. PtmfA has an offset of this + 16 and PtmfC has an offset of this + 28. PtmfXs own data starts at offset this + 40. Thus the following type conversion / upcasting rules apply (here: x is pointer to PtmfX)

  • PtmfX pointer to PtmfA pointer: this + 16
  • PtmfX pointer to PtmfB pointer: this + 28
  • PtmfX pointer to PtmfC pointer: this + 0

PtmfX has also derived all the functions of the base classes. That means PtmfX will (re-)use the derived functions as they are defined in the respective base class. There will be no extra copy of those functions for PtmfX. That's important to know (for the following discussion). Just to clairfy that: PtmfX::learnA is just an alias for PtmfA::learnA. It is the same function!!!

Also important to consider is that in C++ virtual functions can be overwritten by derived classed. In this example PtmfX overwrites the virtual function PtmfC::learnC2 with its own implementation PtmfX:learnC2.

Direct member function invocation

Scenario 1: Before we start the discussion how a ptmf is used to invoce a member function, lets have a look how a "normal" C++ non-virtual member function invocation works.

PtmfX * x = new PtmfX();
...
x->learnA();

Here we are using a PtmfX instance pointer (x) to call member function learnA which was derived from PtmfA. The signature of learnA looks like that:

void PtmfA::learnA();

Imporant to mention at this point, is that learnA belongs to class PtmfA. So within this function, it is expected, that this points to a PtmfA instance. However, we are calling that function using a PtmfX pointer (x). Therefore on invocation, x must be upcasted to an PtmfA * before the CPU can jump to the address of the function learnA. This is all done automatically by the compile, as the compile knows, that the function belongs to PtmfA and the instance pointer is of type PtmfX. Furthermore it knows the address of function learnA.

Scenario 2: If we are calling virtual function, the code looks almost the same:

PtmfX * x = new PtmfX();
...
x->learnC2();

Under the hood the compiler has to do some different things. The implicit type conversion mentiont above has to be done exactly the same way. However the process of getting the address of learnC2 is different. As learnC2 is a virtual function, its destination function function address isn't fix. it depends on the object used on invocation. In this example, we call learnC2 with an PtmfX instance, so we expect, that PtmfX::learnC2 is called. When calling learn" with a differen object - for example with an PtmfWhatever we expect that PtmfWhatever::learnC2 is called (in the case PtmfWhatever has defined its own implmentation of learn2). Therefore getting the address of the right learnC2 function means, accessing the vtable of the object used on invocation, to read out the destination address. The the CPU can jump to that address. Also in this case, everything is done automatically by the compiler, as it has all the required information, to generate the correct code to do the right stuff.

Indirect member function invocation using a ptmf

The same stuff which is done when calling a member function directly must happen when the the member function is called indirectly by the ptmf. It must deal with both scenarios mentioned above.

The assignment of a member function to a ptmf and the later incovation of the ptmf to call the assigned function may happen in totally different pices of code. So the compile can not provide the required information - the ptmf must provide the required information! Thats the reason, why a ptmf stores much more information than a fp. For that reason a ptmfs size is typically twice the size of a fp. And that's also the reason why a ptmf can't stored into a fp, simply as an fp doesn't provide the necessary space to buffer all the information.

With all that in mind, now we can answer the question, how a ptmf works.

On assignment: A ptmf gets...

  • the information wheather it is assigned to a virtual member function or a non-virtual member function.
  • either:
    • the address of the non-virtual member function to be called
    • or the objects vtable-offset that referes to the virtual member function to be called
  • the offset-information how to modify the object pointer to perform the neccessary upcast (to let the object pointer match the type of the assigned function)

On invocation: The object pointer must be modified to upcast to the correct data type. Tthis is done by adding the offset-information out of the ptmf to the object pointer. Then it must be figured out, if the ptmf points to a non-virtual function or a virtual function. If it points to a non-virtual function, the address of the function that shall be called is taken from the ptmf and the CPU jumps to that address. If the ptmf points to a virual function, the objects vtable is read at an offset, that is taken from the ptmf. This read provides the address the CPU then jumps to.

Practical Example

This repository provides a little programm, that can be used for a practical understanding of this paper. The following listing shows the output of the programm complled with a 32-MinGW GCC, and running on a Windows maschine. The program implements the four classes mentioned above. It lists their this pointer, the object size and the offsets to the data attributes. Most interessting part is the output of class PtmfX which inherits from the other classes. The listing shows the OFFSETs a PtmfX pointer must be adjusted to perform the respective updast. The output shows that the ptmf is composed out of two pointers. It also shows the content of a ptmf when assigned to the respective member function. Here, the "first-ptmf-pointer" contains the offset-information how to modify the object pointer on invo

Related Skills

View on GitHub
GitHub Stars19
CategoryEducation
Updated2mo ago
Forks3

Languages

C++

Security Score

80/100

Audited on Jan 30, 2026

No findings