Re: Fun with member-function pointers
Default User wrote:
========= code =========
#include <iostream>
#define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
class tbase
{
public:
tbase() : p(0)
{
}
typedef int (tbase::*HandlerPtr)(int);
protected:
HandlerPtr p;
};
class test : tbase
{
public:
test()
{
p = static_cast<HandlerPtr>(&test::f);
This is undefined behavior. It only happens to work as long as pointers
to tbase and pointers to test are binary the same.
}
int f(int i)
{
std::cout << "f: " << i << "\n";
return i;
}
This impplementation of f does not access this and will not fail when
this is bad.
void m()
{
CALL_HANDLER(*this, p)(1);
}
};
int main()
{
test t;
t.m();
return 0;
}
To illustrate the point: change your code to (see below) and it will
sadly fail.
I would like to have this pattern supported by the language too, but
there is an important implementation issue.
Any function of type HandlerPtr expects an object of type tbase* as this
argument, while the function test::f expects an object of type test* as
this argument. Normally when the tbase slice of test is accessed the
compiler generates the required code to access the slice. But this time
is the other way around (contravariance). The compiler converts test* to
tbase* to call the function pointer which claims to need tbase*, but the
function behind p expects test*. Ouch, who does the downcast?
Also how should the compiler ensure that the this pointer used to invoke
a function pointer of type HandlerPtr is of type test*. HandlerPtr tells
you that it is sufficient that you are of type tbase*. So the downcast
of this to test* might not be allowed at all.
Think of two static functions
void (*funcptr1)(tbase*)
void (*funcptr2)(test*)
These pointers are incompatible too for the same reason.
What you need to do the above conversion is a proxy function that
converts tbase* back to the test*. And of course, this proxy function is
only allowed, if p is invoked only with objects of type test.
Member function pointers are not the solution of your problem. You
should use an observer pattern instead.
Member function pointers are the other way around. A pointer of type
void(test::*)() can be used to call functions of type void(tbase::*)(),
since every function that expects tbase* can also be invoked with test*.
The compiler will handle the necessary conversion at each invocation of
the function pointer. For this reason member function pointers are
larger than one machine size word in general. The compiler usually adds
offset information for this. (Things get even more complicated if tbase
is a virtual base class of test.)
-------------
#include <iostream>
#define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))
class tbase
{
protected:
int x;
public:
tbase() : p(0), x(99)
{
}
typedef int (tbase::*HandlerPtr)(int);
protected:
HandlerPtr p;
};
class test : public tbase
{
public:
test()
{
p = static_cast<HandlerPtr>(&test::f);
}
virtual ~test()
{ std::cout << "~test\n";
}
int f(int i)
{
std::cout << "f: " << i << "\n";
std::cout << "x=" << x << "\n"; // ouch!
return i;
}
void m()
{
CALL_HANDLER(*this, p)(1);
}
};
int main()
{
test t;
t.m();
return 0;
}