If you're looking for performance, complexity, or many possible solutions to solve a problem, C ++ is always a good candidate when it comes to extremes. Of course, functionality usually comes with complexity, but some C++ peculiarities are almost illegible. From my point of view, C++ method pointers may be the most complex expressions I've ever come across, but I'll start with something simpler.
The examples in this article are available in my GitHub repository.
C: Pointer to functions
Let's begin with some basics: Assume you have a function that takes two integers as arguments and returns an integer:
int sum(int a, intb){
return a+b;
}
In plain C, you can create a pointer to this function, assign it to your sum(...)
function, and call it by dereferencing. The function's signature (arguments, return type) must comply with the pointer's signature. Aside from that, a function pointer behaves like an ordinary pointer:
int (*funcPtrOne)(int, int);
funcPtrOne = ∑
int resultOne = funcPtrOne(2, 5);
It gets a bit uglier if you take a pointer as an argument and return a pointer:
int *next(int *arrayOfInt){
return ++arrayOfInt;
}
int *(*funcPtrTwo)(int *intPtr);
funcPtrTwo = &next;
int resultTwo = *funcPtrTwo(&array[0]);
Function pointers in C store the address of a subroutine.
Pointers to methods
Let's step into C++: The good news is that you probably won't need to use pointers to methods, except in a few rare cases, like the following one. First, define a class with member functions you already know:
class MyClass
{
public:
int sum(int a, int b) {
return a+b;
}
};
1. Define a pointer to a method of a certain class type
Declare a pointer to a method of the MyClass
type. At this point, you don't know the exact method you want to call. You've only declared a pointer to some arbitrary MyClass
method. Of course, the signature (arguments, return type) matches the sum(…)
method you want to call later:
int (MyClass::*methodPtrOne)(int, int);
2. Assign a certain method
In contrast to C (or static member functions), method pointers don't point to absolute addresses. Each class type in C++ has a virtual method table (vtable) that stores the address offset for each method. A method pointer refers to a certain entry in the vtable, so it also stores only the offset value. This principle also enables dynamic dispatch.
Because the signature of the sum(…)
method matches your pointer's declaration, you can assign the signature to it:
methodPtrOne = &MyClass::sum;
3. Invoke the method
If you want to invoke the method with the pointer, you have to provide an instance of the class type:
MyClass clsInstance;
int result = (clsInstance.*methodPtrOne)(2,3);
You can access the instance with the .
operator, dereference the pointer with a *
, and thus call the method by providing two integers as arguments. Ugly, right? But you can still go a step further.
Using method pointers within a class
Assume you are creating an application with a client/server principle architecture with a backend and a frontend. You don't care about the backend for now; instead, you will focus on the frontend, which is based on a C++ class. The frontend's complete initialization relies on data provided by the backend, so you need an additional initialization mechanism. Also, you want to implement this mechanism generically so that you can extend your frontend with other initialization methods in the future (maybe dynamically).
First, define a data type that can store a method pointer to an initialization method (init
) and the information describing when this method should be called (ticks
):
template<typename T>
struct DynamicInitCommand {
void (T::*init)(); // Pointer to additional initialization method
unsigned int ticks; // Number of ticks after init() is called
};
Here is what the Frontend
class looks like:
class Frontend
{
public:
Frontend(){
DynamicInitCommand<Frontend> init1, init2, init3;
init1 = { &Frontend::dynamicInit1, 5};
init2 = { &Frontend::dynamicInit2, 10};
init3 = { &Frontend::dynamicInit3, 15};
m_dynamicInit.push_back(init1);
m_dynamicInit.push_back(init2);
m_dynamicInit.push_back(init3);
}
void tick(){
std::cout << "tick: " << ++m_ticks << std::endl;
/* Check for delayed initializations */
std::vector<DynamicInitCommand<Frontend>>::iterator it = m_dynamicInit.begin();
while (it != m_dynamicInit.end()){
if (it->ticks < m_ticks){
if(it->init)
((*this).*(it->init))(); // here it is
it = m_dynamicInit.erase(it);
} else {
it++;
}
}
}
unsigned int m_ticks{0};
private:
void dynamicInit1(){
std::cout << "dynamicInit1 called" << std::endl;
};
void dynamicInit2(){
std::cout << "dynamicInit2 called" << std::endl;
}
void dynamicInit3(){
std::cout << "dynamicInit3 called" << std::endl;
}
unsigned int m_initCnt{0};
std::vector<DynamicInitCommand<Frontend> > m_dynamicInit;
};
After Frontend
is instantiated, the tick()
method is called at fixed intervals by the backend. For example, you can call it every 200ms:
int main(int argc, char* argv[]){
Frontend frontendInstance;
while(true){
frontendInstance.tick(); // just for simulation purpose
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
Frontend
has three additional initialization methods that must be called based on the value of m_ticks
. The information about which initialization method to call at which tick is stored in the vector m_dynamicInit
. In the constructor (Frontend()
), append this information to the vector so that the additional initialization functions are called after five, 10, and 15 ticks. When the backend calls the tick()
method, the value m_ticks
is incremented, and you iterate over the vector m_dynamicInit
to check whether an initialization method has to be called.
If this is the case, the method pointer must be dereferenced by referring to this
:
((*this).*(it->init))()
Summary
Methods pointers can get a bit complicated if you're not familiar with them. I did a lot of trial and error, and it took time to find the correct syntax. However, once you understand the general principle, method pointers become less terrifying.
This is the most complex syntax I have found in C++ so far. Do you know something even worse? Post it in the comments!
3 Comments