Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Fun with member-function pointers

Reply
Thread Tools

Fun with member-function pointers

 
 
Default User
Guest
Posts: n/a
 
      08-22-2011
I've been working on a project that implements instrument drivers. The API
uses operation codes to direct behavior. All the drivers are subclasses of a
common ABC. In my implementation, I used a method of mapping the op codes
to handlers for that operation. That was accomplished with pointers to
member functions.

Someone asked me why I didn't have the handler storage and lookup in the
base class. My initial response was that the pointers had different types.
Then I got thinking that such a condition doesn't normally slow us down. So
I created a test case (skipping the map and all).

This worked, in that it built and seem to run ok. I'm not overly thrilled
with the cast necessary to store the func pointer. Does anyone see a
problem? Improvements?


Brian

========= code =========

#include <iostream>

#define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))

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);
}
int f(int i)
{
std::cout << "f: " << i << "\n";
return i;
}
void m()
{
CALL_HANDLER(*this, p)(1);
}
};

int main()
{
test t;
t.m();
return 0;
}


 
Reply With Quote
 
 
 
 
Victor Bazarov
Guest
Posts: n/a
 
      08-22-2011
On 8/22/2011 5:41 PM, Default User wrote:
> I've been working on a project that implements instrument drivers. The API
> uses operation codes to direct behavior. All the drivers are subclasses of a
> common ABC. In my implementation, I used a method of mapping the op codes
> to handlers for that operation. That was accomplished with pointers to
> member functions.
>
> Someone asked me why I didn't have the handler storage and lookup in the
> base class. My initial response was that the pointers had different types.
> Then I got thinking that such a condition doesn't normally slow us down. So
> I created a test case (skipping the map and all).
>
> This worked, in that it built and seem to run ok. I'm not overly thrilled
> with the cast necessary to store the func pointer. Does anyone see a
> problem? Improvements?
>
>
> Brian
>
> ========= code =========
>
> #include<iostream>
>
> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))


I'd probably tried to change this to

CALL_HANDLER(o, p, args) ((o).*(p)) args

(see below for the use).

>
> class tbase
> {
> public:
> tbase() : p(0)


Perhaps (a) it shouldn't be public (do you intend to instantiate this?
It's an ABC, isn't it?) and (b) it should have the argument to init the
'p' with (defautl to 0):

protected:
tbase(HandlerPtr pp) : p(pp) {}

> {
> }


Move the typedef above the c-tor:

> typedef int (tbase::*HandlerPtr)(int);
> protected:
> HandlerPtr p;
> };
> class test : tbase
> {
> public:
> test()
> {
> p = static_cast<HandlerPtr>(&test::f);


Shouldn't you use the initializer list instead of the assignment? See FAQ.

> }
> int f(int i)
> {
> std::cout<< "f: "<< i<< "\n";
> return i;
> }
> void m()
> {
> CALL_HANDLER(*this, p)(1);


The use
CALL_HANLDER((this, p, (1));

> }
> };
>
> int main()
> {
> test t;
> t.m();
> return 0;
> }


There is no real problem yet. Now imagine that somebody thinks of
deriving from your 'test' and providing their own handler? How would
they do it? Or can that never happen?

Another possible solution is to actually have a pointer to function
instead of pointer to member, and cast 'this' to the specific class,
which is usually "safer", I think. Not that I know much about that...

V
--
I do not respond to top-posted replies, please don't ask
 
Reply With Quote
 
 
 
 
Default User
Guest
Posts: n/a
 
      08-23-2011

"Victor Bazarov" <(E-Mail Removed)> wrote in message
news:j2uoi5$jgn$(E-Mail Removed)...
> On 8/22/2011 5:41 PM, Default User wrote:
> I'd probably tried to change this to
>
> CALL_HANDLER(o, p, args) ((o).*(p)) args


What! You dare question the CLC++ FAQ list where I copied that from? I'll
look at it.

> (see below for the use).
>
>>
>> class tbase
>> {
>> public:
>> tbase() : p(0)

>
> Perhaps (a) it shouldn't be public (do you intend to instantiate this?
> It's an ABC, isn't it?) and (b) it should have the argument to init the
> 'p' with (defautl to 0):


This was just a quicky demo. So I didn't bother with some of that sort of
thing. I'll check the "real" code though.

>> class test : tbase
>> {
>> public:
>> test()
>> {
>> p = static_cast<HandlerPtr>(&test::f);

>
> Shouldn't you use the initializer list instead of the assignment? See
> FAQ.


For this case, sure. But the actual code will be putting them in a std::map
with the op codes as indexes.

> There is no real problem yet. Now imagine that somebody thinks of
> deriving from your 'test' and providing their own handler? How would they
> do it? Or can that never happen?


I guess I'm not sure what the specific problem you're concerned about. I
fear my quicky proof-of-concept demo might have been too sketchy.

> Another possible solution is to actually have a pointer to function
> instead of pointer to member, and cast 'this' to the specific class, which
> is usually "safer", I think. Not that I know much about that...


I don't think so. One goal is to push a lot of behavior down to the base
class. I can make the handler map and the handler calling function go down
there. Then the derived class will populate the map with its specific
handler for each op code. The upper level modules just call with the op
codes and data.

I'll look to see if I can rework my demo tomorrow to make it a bit more like
the real use case.



Brian


 
Reply With Quote
 
Marcel Müller
Guest
Posts: n/a
 
      08-23-2011
Default User wrote:
> ========= code =========
>
> #include <iostream>
>
> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))
>
> 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).*(ptrToM ember))

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;
}
 
Reply With Quote
 
Juha Nieminen
Guest
Posts: n/a
 
      08-23-2011
Default User <(E-Mail Removed)> wrote:
> #include <iostream>
>
> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))
>
> 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);
> }
> int f(int i)
> {
> std::cout << "f: " << i << "\n";
> return i;
> }
> void m()
> {
> CALL_HANDLER(*this, p)(1);
> }
> };


My knowledge of the C++ standard fails me at this point. Is it really
legal to store a method pointer of the derived class type into a method
pointer variable of the base class type, and then call it like that?

(I'm pretty certain that if the base class tried to call the method
through the pointer, using itself as the object, that would be UB (or
similar). However, in this case it's the derived class that is doing the
calling, so I'm not completely sure anymore.)
 
Reply With Quote
 
Juha Nieminen
Guest
Posts: n/a
 
      08-23-2011
Marcel Müller <(E-Mail Removed)> wrote:
>> 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.


Could you elaborate? After all, (&test::f) is a pointer to a function,
not a pointer to an object. Calling the function through that pointer
might be UB, but is just storing it too?
 
Reply With Quote
 
Paul
Guest
Posts: n/a
 
      08-23-2011
On Aug 22, 10:41*pm, "Default User" <(E-Mail Removed)> wrote:
> I've been working on a project that implements instrument drivers. The API
> uses operation codes to direct behavior. All the drivers are subclasses of a
> common ABC. *In my implementation, I used a method of mapping the op codes
> to handlers for that operation. That was accomplished with pointers to
> member functions.
>
> Someone asked me why I didn't have the handler storage and lookup in the
> base class. My initial response was that the pointers had different types..
> Then I got thinking that such a condition doesn't normally slow us down. So
> I created a test case (skipping the map and all).
>
> This worked, in that it built and seem to run ok. I'm not overly thrilled
> with the cast necessary to store the func pointer. Does anyone see a
> problem? Improvements?
>
> Brian
>
> ========= code =========
>
> #include <iostream>
>
> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))
>
> 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);
> * *}
> * *int f(int i)
> * *{
> * * * std::cout << "f: " << i << "\n";
> * * * return i;
> * *}
> * *void m()
> * *{
> * * * CALL_HANDLER(*this, p)(1);
> * *}
>
> };
>
> int main()
> {
> * *test t;
> * *t.m();
> * *return 0;
>
>
>
> }- Hide quoted text -
>
> - Show quoted text -


Hi

Would something like this be any use to you?

class Base{
public:
virtual void foo(int i) =0;
};

class Derivedublic Base{
public:
typedef void (Derived::*funcp)(int);
Derived(){
arr[0]= &Derived::fun1;
arr[1]= &Derived::fun2;
arr[2]= &Derived::fun3;
}
void foo(int i){
(this->*arr[i])(999);
}
void fun1(int i){std::cout<<"In fun1.."<<i<<"..\n";}
void fun2(int i){std::cout<<"In fun2.."<<i<<"..\n";}
void fun3(int i){std::cout<<"In fun3.."<<i<<"..\n";}
private:
funcp arr[3];
};

int main() {
Base* p = new Derived;
p->foo(1);
}


Im not sure on the details of your program so its hard to understand
you API requirments and stuff.
HTH
 
Reply With Quote
 
Victor Bazarov
Guest
Posts: n/a
 
      08-23-2011
On 8/23/2011 3:09 AM, Juha Nieminen wrote:
> Default User<(E-Mail Removed)> wrote:
>> #include<iostream>
>>
>> #define CALL_HANDLER(object,ptrToMember)((object).*(ptrToM ember))
>>
>> 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);
>> }
>> int f(int i)
>> {
>> std::cout<< "f: "<< i<< "\n";
>> return i;
>> }
>> void m()
>> {
>> CALL_HANDLER(*this, p)(1);
>> }
>> };

>
> My knowledge of the C++ standard fails me at this point. Is it really
> legal to store a method pointer of the derived class type into a method
> pointer variable of the base class type, and then call it like that?
>
> (I'm pretty certain that if the base class tried to call the method
> through the pointer, using itself as the object, that would be UB (or
> similar). However, in this case it's the derived class that is doing the
> calling, so I'm not completely sure anymore.)


Analysing... Analysing...

My guess is that this "round-trip" (store in the base, but call in the
derived where the member function actually exists) might be actually a
bad idea, and here is why. The 'static_cast' used to convert to 'p'
*can* change the pointer itself (adjust or whatever). Calling the
member from inside the derived class does not change the fact that the
type of the pointer is "member of 'tbase'". Only an implicit cast to
"member of 'test'" could bring it back, so to speak. IOW, the 'm'
function should contain the declaration and use of test::*, converted
from 'p', something like:

void m()
{
void (test::*pp)(int) = p;
CALL_HANDLER(*this, pp)(1);
}

Then the conversions (to pointer to member of base, to pointer to member
of derived) are mutually cancelling.

V
--
I do not respond to top-posted replies, please don't ask
 
Reply With Quote
 
Marcel Müller
Guest
Posts: n/a
 
      08-23-2011
Juha Nieminen wrote:
> Marcel Müller <(E-Mail Removed)> wrote:
>>> 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.

>
> Could you elaborate? After all, (&test::f) is a pointer to a function,
> not a pointer to an object. Calling the function through that pointer
> might be UB, but is just storing it too?


Well, the language does no longer define the semantic of the pointer
stored by this expression. What else would you call undefined behavior?

Of course, it is not likely to cause harm unless you dereference it. But
AFAIK the round trip - casting to an incompatible pointer and back to
the original one - is not guaranteed to work. The only exception is
casting any object pointer to void* and back to whatever it has been before.


Marcel
 
Reply With Quote
 
Juha Nieminen
Guest
Posts: n/a
 
      08-23-2011
Victor Bazarov <(E-Mail Removed)> wrote:
> void (test::*pp)(int) = p;


Can you do that? I haven't tried, but it looks to me like there's an
attempt to implicitly convert a method pointer of the base class type to
a method pointer of the derived class type. Is that allowed? (After all,
if you try to implicitly convert a base class pointer to a derived class
pointer, you'll get an error.)

The only way I know of for safely storing derived-type method pointers
in the base class is for the base class to implement a template wrapper
type which encloses the derived-type pointer (and which the derived class
instantiates). The base class can call this without itself having to be
a template class, if this wrapper itself is derived from a non-templated
base wrapper, and the derived type instantiation is done dynamically and
managed by the base class. A virtual function in this base wrapper can
then be used to call the method from the base class.

Yeah, it's complicated.
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
pointers, pointers, pointers... cerr C Programming 12 04-07-2011 11:17 PM
3 PIX VPN questions - FUN FUN FUN frishack@gmail.com Cisco 3 03-16-2006 02:25 PM
OT: Wednesday follow-up-to-Tuesday-Fun Fun Ken Briscoe MCSE 0 07-14-2004 01:41 PM
Programming is not as much fun/more fun than it used to be. Andy Fish Java 65 05-18-2004 08:24 PM
Fun fun fun Luke Computer Support 3 10-07-2003 03:45 PM



Advertisments