Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #9208 > unrolled thread

Fun with member-function pointers

Started by"Default User" <defaultuserbr@yahoo.com>
First post2011-08-22 16:41 -0500
Last post2011-08-25 11:29 -0400
Articles 20 on this page of 37 — 9 participants

Back to article view | Back to comp.lang.c++


Contents

  Fun with member-function pointers "Default User" <defaultuserbr@yahoo.com> - 2011-08-22 16:41 -0500
    Re: Fun with member-function pointers Victor Bazarov <v.bazarov@comcast.invalid> - 2011-08-22 19:25 -0400
      Re: Fun with member-function pointers "Default User" <defaultuserbr@yahoo.com> - 2011-08-22 19:06 -0500
    Re: Fun with member-function pointers Juha Nieminen <nospam@thanks.invalid> - 2011-08-23 07:09 +0000
      Re: Fun with member-function pointers Victor Bazarov <v.bazarov@comcast.invalid> - 2011-08-23 08:04 -0400
        Re: Fun with member-function pointers Juha Nieminen <nospam@thanks.invalid> - 2011-08-23 13:34 +0000
          Re: Fun with member-function pointers Victor Bazarov <v.bazarov@comcast.invalid> - 2011-08-23 13:02 -0400
        Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-23 19:12 -0700
      Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-23 18:51 -0700
    Re: Fun with member-function pointers Marcel Müller <news.5.maazl@spamgourmet.com> - 2011-08-23 09:07 +0200
      Re: Fun with member-function pointers Juha Nieminen <nospam@thanks.invalid> - 2011-08-23 10:09 +0000
        Re: Fun with member-function pointers Marcel Müller <news.5.maazl@spamgourmet.com> - 2011-08-23 15:04 +0200
      Re: Fun with member-function pointers "Default User" <defaultuserbr@yahoo.com> - 2011-08-23 10:36 -0500
        Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-23 18:46 -0700
          Re: Fun with member-function pointers "Default User" <defaultuserbr@yahoo.com> - 2011-08-24 09:33 -0500
            Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-24 08:32 -0700
              Re: Fun with member-function pointers "Default User" <defaultuserbr@yahoo.com> - 2011-08-24 11:15 -0500
                Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-24 09:30 -0700
        Re: Fun with member-function pointers Marcel Müller <news.5.maazl@spamgourmet.com> - 2011-08-24 12:27 +0200
          Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-24 06:46 -0700
            Re: Fun with member-function pointers Marcel Müller <news.5.maazl@spamgourmet.org> - 2011-08-24 18:57 +0200
              Re: Fun with member-function pointers Paul <pchristor@yahoo.co.uk> - 2011-08-24 13:08 -0700
                Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-24 13:44 -0700
                  Re: Fun with member-function pointers Paul <pchristor@yahoo.co.uk> - 2011-08-25 04:23 -0700
                    Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-25 07:31 -0700
                    Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-25 08:21 -0700
                      Re: Fun with member-function pointers Paul <pchristor@yahoo.co.uk> - 2011-08-25 10:50 -0700
                  Re: Fun with member-function pointers Leigh Johnston <leigh@i42.co.uk> - 2011-08-25 13:57 +0100
                    Re: Fun with member-function pointers Leigh Johnston <leigh@i42.co.uk> - 2011-08-25 14:08 +0100
                      Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-25 07:32 -0700
                        Re: Fun with member-function pointers Leigh Johnston <leigh@i42.co.uk> - 2011-08-25 15:45 +0100
                        Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-25 07:48 -0700
      Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-23 18:43 -0700
    Re: Fun with member-function pointers Paul <pchristor@yahoo.co.uk> - 2011-08-23 03:49 -0700
      Re: Fun with member-function pointers Paul <pchristor@yahoo.co.uk> - 2011-08-23 06:44 -0700
    Re: Fun with member-function pointers Andrey Tarasevich <andreytarasevich@hotmail.com> - 2011-08-23 18:28 -0700
    Re: Fun with member-function pointers Jeff Flinn <TriumphSprint2000@hotmail.com> - 2011-08-25 11:29 -0400

Page 1 of 2  [1] 2  Next page →


#9208 — Fun with member-function pointers

From"Default User" <defaultuserbr@yahoo.com>
Date2011-08-22 16:41 -0500
SubjectFun with member-function pointers
Message-ID<9bg0ojFsapU1@mid.individual.net>
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).*(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);
   }
   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;
}

[toc] | [next] | [standalone]


#9210

FromVictor Bazarov <v.bazarov@comcast.invalid>
Date2011-08-22 19:25 -0400
Message-ID<j2uoi5$jgn$1@dont-email.me>
In reply to#9208
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).*(ptrToMember))

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

[toc] | [prev] | [next] | [standalone]


#9212

From"Default User" <defaultuserbr@yahoo.com>
Date2011-08-22 19:06 -0500
Message-ID<9bg98kFlfoU1@mid.individual.net>
In reply to#9210
"Victor Bazarov" <v.bazarov@comcast.invalid> wrote in message 
news:j2uoi5$jgn$1@dont-email.me...
> 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

[toc] | [prev] | [next] | [standalone]


#9217

FromJuha Nieminen <nospam@thanks.invalid>
Date2011-08-23 07:09 +0000
Message-ID<4e535238$0$3087$7b1e8fa0@news.nbl.fi>
In reply to#9208
Default User <defaultuserbr@yahoo.com> wrote:
> #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);
>   }
>   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.)

[toc] | [prev] | [next] | [standalone]


#9230

FromVictor Bazarov <v.bazarov@comcast.invalid>
Date2011-08-23 08:04 -0400
Message-ID<j30516$jk7$1@dont-email.me>
In reply to#9217
On 8/23/2011 3:09 AM, Juha Nieminen wrote:
> Default User<defaultuserbr@yahoo.com>  wrote:
>> #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);
>>    }
>>    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

[toc] | [prev] | [next] | [standalone]


#9232

FromJuha Nieminen <nospam@thanks.invalid>
Date2011-08-23 13:34 +0000
Message-ID<4e53ac49$0$4364$7b1e8fa0@news.nbl.fi>
In reply to#9230
Victor Bazarov <v.bazarov@comcast.invalid> 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.

[toc] | [prev] | [next] | [standalone]


#9241

FromVictor Bazarov <v.bazarov@comcast.invalid>
Date2011-08-23 13:02 -0400
Message-ID<j30mek$cjn$1@dont-email.me>
In reply to#9232
On 8/23/2011 9:34 AM, Juha Nieminen wrote:
> Victor Bazarov<v.bazarov@comcast.invalid>  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.)

Yes, it's allowed.  And implicit conversion for pointers to members 
works exactly the opposite way to pointers to objects.  Objects: if it's 
a pointer to derived, it can be converted to pointer to base, members: 
if it's a member of base, it can be implicitly converted to member of 
the derived.  Reverse conversions require a static cast, which should 
succeed (and well-defined) if the original implicit conversion exists.


It's actually logical, if you think about it.  A derived class does 
contain the base class subobject, a pointer to which can easily be 
figured out (calculated) by the compiler.  And if there is a member in 
the base class, a way to access it through the derived object can also 
be easily figured out (by adding a conversion from 'derived' to 'base').

As to the validity of the round-trip, I am not so sure.

>    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.

No joke...

V
-- 
I do not respond to top-posted replies, please don't ask

[toc] | [prev] | [next] | [standalone]


#9270

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-23 19:12 -0700
Message-ID<j31mls$r3r$1@dont-email.me>
In reply to#9230
On 8/23/2011 5:04 AM, Victor Bazarov wrote:
>...
> Then the conversions (to pointer to member of base, to pointer to member
> of derived) are mutually cancelling.
> ...

That's great, but the fact is that C++ language allows absolutely 
unrestricted `static_cast`-ing if member function pointers up the 
hierarchy. Also, at the point where the pointer gets dereferenced (i.e. 
at the point of the call) the only requirement that C++ language imposes 
is that the _dynamic_ type of the object used on the left-hand side of 
`->*`/`.*` operators shall contain the member the pointer is pointing 
to. There are no other restrictions whatsoever.

In order to implement this specification one has to know how to do the 
proper adjustment of `this` value at the point of the call. How is it 
done? It is done by compiler magic.

In the OPs case it is easy, since no adjustment is needed.

In the allegedly "non-working" example posted by Marcel Müller it is 
also pretty very easy. In a typical implementation the pointer object 
itself stores an extra "`this` adjustment offset" value, which is used 
at run-time to obtain the proper `this` value for the call. This is also 
sufficient for multiple inheritance situations.

Things usually become difficult and heavy in virtual inheritance 
situations. However, this is still doable and every compliant compiler 
does it.

-- 
Best regards,
Andrey Tarasevich

P.S. It actually appears that MSVC++ compiler is broken to the point 
where it can't handle Marcel Müller's example regardless of the 
settings. GCC on the other hand has no issues with it whatsoever.

[toc] | [prev] | [next] | [standalone]


#9266

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-23 18:51 -0700
Message-ID<j31lea$l1c$1@dont-email.me>
In reply to#9217
On 8/23/2011 12:09 AM, Juha Nieminen wrote:
>
>    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?

Yes. It is absolutely legal. This is described explicitly in the 
specification of `static_cast`. This is one of the things `static_cast` 
is for.

>    (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).

Exactly. This is described explicitly in the specification of `->*`/`.*` 
operators.

> However, in this case it's the derived class that is doing the
> calling, so I'm not completely sure anymore.)

It doesn't really matter who is doing the calling.

This part of `static_cast` specification was added to C++ between the 
last draft of C++98 and the final version of C++98. So, in the last 
draft it is still illegal, but the actual standard itself explicitly 
supports it. For this reason, one can come across pre-standard Usenet 
post from some quite respected individuals claiming that this is illegal 
(which often fuels the confusion). In reality, this is allowed in 
standard C++.

-- 
Best regards,
Andrey Tarasevich

[toc] | [prev] | [next] | [standalone]


#9218

FromMarcel Müller <news.5.maazl@spamgourmet.com>
Date2011-08-23 09:07 +0200
Message-ID<4e5351c3$0$6566$9b4e6d93@newsspool3.arcor-online.net>
In reply to#9208
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;
}

[toc] | [prev] | [next] | [standalone]


#9227

FromJuha Nieminen <nospam@thanks.invalid>
Date2011-08-23 10:09 +0000
Message-ID<4e537c67$0$3065$7b1e8fa0@news.nbl.fi>
In reply to#9218
Marcel Müller <news.5.maazl@spamgourmet.com> 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?

[toc] | [prev] | [next] | [standalone]


#9231

FromMarcel Müller <news.5.maazl@spamgourmet.com>
Date2011-08-23 15:04 +0200
Message-ID<4e53a578$0$6572$9b4e6d93@newsspool3.arcor-online.net>
In reply to#9227
Juha Nieminen wrote:
> Marcel Müller <news.5.maazl@spamgourmet.com> 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

[toc] | [prev] | [next] | [standalone]


#9235

From"Default User" <defaultuserbr@yahoo.com>
Date2011-08-23 10:36 -0500
Message-ID<9bhvopFfssU1@mid.individual.net>
In reply to#9218
"Marcel Müller" <news.5.maazl@spamgourmet.com> wrote in message 
news:4e5351c3$0$6566$9b4e6d93@newsspool3.arcor-online.net...

> This is undefined behavior.

I think that I'm convinced that you're correct. The example I had built 
without issue and gave me the results I wanted, but as we know UB can do 
that. Right up until someone changes something and then it doesn't.

> To illustrate the point: change your code to (see below) and it will sadly 
> fail.

You are correct, although adding a virtual destructor to the base class 
makes it "work" again.

> Member function pointers are not the solution of your problem. You should 
> use an observer pattern instead.

I'm not sure how that pattern would apply to this situation. Perhaps I'll 
start a new topic on the more general question.

If nothing else, what I have now works and is safe. It just leads to 
repetitive code in all the modules (eventually ten or more). I had hoped to 
push this down to the base class.



Brian

[toc] | [prev] | [next] | [standalone]


#9265

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-23 18:46 -0700
Message-ID<j31l4o$iso$2@dont-email.me>
In reply to#9235
On 8/23/2011 8:36 AM, Default User wrote:
>
>> This is undefined behavior.
>
> I think that I'm convinced that you're correct. The example I had built
> without issue and gave me the results I wanted, but as we know UB can do
> that. Right up until someone changes something and then it doesn't.

No. That poster is flat out incorrect.

>> To illustrate the point: change your code to (see below) and it will sadly
>> fail.
>
> You are correct, although adding a virtual destructor to the base class
> makes it "work" again.

Again, incorrect. The code that supposedly "fails" doesn't fail at all 
:) As I said already, a spurious failure can be caused by incorrect 
compiler setup. Some compilers (like MSVC++) behave non-compliantly in 
this regard by default. However, if you use proper compiler setting 
(i.e. if you use a standard compliant compiler) the code will work.

-- 
Best regards,
Andrey Tarasevich

[toc] | [prev] | [next] | [standalone]


#9289

From"Default User" <defaultuserbr@yahoo.com>
Date2011-08-24 09:33 -0500
Message-ID<9bkgdaF95nU1@mid.individual.net>
In reply to#9265
"Andrey Tarasevich" <andreytarasevich@hotmail.com> wrote in message 
news:j31l4o$iso$2@dont-email.me...
> On 8/23/2011 8:36 AM, Default User wrote:
>>
>>> This is undefined behavior.
>>
>> I think that I'm convinced that you're correct. The example I had built
>> without issue and gave me the results I wanted, but as we know UB can do
>> that. Right up until someone changes something and then it doesn't.
>
> No. That poster is flat out incorrect.

Very interesting. Below will be a more complete example of what I'm 
proposing to do in the real code. That is, the managing of the handlers, 
storing the pointers, retrieving the correct one based on op code, and 
calling of the function pointer, take place in the base class. All the 
derived class has to do is set up the relationships between op codes and its 
member function pointers to implement the operation set for that particular 
module.

If this is still legal, then I'd be on pretty firm ground with my plan for 
the actual code.


Brian

#include <iostream>
#include <map>
#define CALL_HANDLER(object,ptrToMember)((object).*(ptrToMember))

class tbase
{
public:
   typedef int (tbase::*HandlerPtr)(int);
   void write(int data, int code)
   {
      std::map<int, HandlerPtr>::iterator it;
      it = handlers.find(code);
      if (it != handlers.end())
      {
         // Found, call the function pointer
         CALL_HANDLER(*this, it->second)(data);
      }
      else // unhandled code
      {
         // error handling
      }
   }
   void list()
   {
      std::map<int, HandlerPtr>::iterator it = handlers.begin();

      std::cout << "Supported Op codes:\n";
      while (it != handlers.end())
      {
         std::cout << it->first << "\n";
         it++;
      }
   }
protected:
   tbase() : baseDataStore(0)
   {
   }
   virtual ~tbase()
   {
   }
   std::map<int, HandlerPtr> handlers;
   int baseDataStore;
};

class test : public tbase
{
public:
   test() : testDataStore(0)
   {
      handlers[0] = static_cast<HandlerPtr>(&test::H0);
      handlers[4] = static_cast<HandlerPtr>(&test::H4);
   }
   virtual ~test()
   {
   }
   virtual int H0(int data)
   {
      baseDataStore = data;
      std::cout << "test::H0: " << baseDataStore << "\n";
      return data;
   }
   int H4(int data)
   {
      testDataStore = data;
      std::cout << "test::H4: " << testDataStore << "\n";
      return data;
   }
private:
   int testDataStore;
};

int main()
{
   test t;
   t.list();
   t.write(123, 0);
   t.write(456, 4);
   return 0;
}

[toc] | [prev] | [next] | [standalone]


#9291

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-24 08:32 -0700
Message-ID<j335g1$qs3$1@dont-email.me>
In reply to#9289
Default User wrote:
> 
> Very interesting. Below will be a more complete example of what I'm 
> proposing to do in the real code. That is, the managing of the handlers, 
> storing the pointers, retrieving the correct one based on op code, and 
> calling of the function pointer, take place in the base class. All the 
> derived class has to do is set up the relationships between op codes and its 
> member function pointers to implement the operation set for that particular 
> module.
> 
> If this is still legal, then I'd be on pretty firm ground with my plan for 
> the actual code.

The code you have in your example is perfectly legal, with perfectly 
defined behavior.

Again, beware of broken compilers that might not handle it correctly. 
But for your specific example I don't expect any surprises from the 
compiler.

Keep in mind though, that one restriction imposed on such use of 
`static_cast` is that the base class should be unambiguous  and 
non-virtual in the derived class. (I was wrong to say earlier that 
virtual inheritance in not a problem in this case).

For example, this code will not compile because the base class is ambiguous

   struct B { };
   struct D1 : B {};
   struct D2 : B {};
   struct X : D1, D2 { void foo(); };

   int main() {
     void (B::*p)() = static_cast<void (B::*)()>(&X::foo); // ERROR
   }

And this code will not compile because the base class is virtual

   struct B { };
   struct D : virtual B { void foo(); };

   int main() {
     void (B::*p)() = static_cast<void (B::*)()>(&D::foo); // ERROR
   }

As long as your base class is not involved in such inheritance contexts, 
your code is fine. Note, again, that the above situations will fail to 
compile (as opposed to compiling and producing undefined behavior later).

Introduction of VMT pointer late in the hierarchy (as in Marcel's 
example) as well as multiple inheritance (aside from ambiguous 
situations illustrated above) do not really break this functionality. 
The compilers are required to make it work correctly.

-- 
Best regards,
Andrey Tarasevich

[toc] | [prev] | [next] | [standalone]


#9292

From"Default User" <defaultuserbr@yahoo.com>
Date2011-08-24 11:15 -0500
Message-ID<9bkmd6Fm6pU1@mid.individual.net>
In reply to#9291
"Andrey Tarasevich" <andreytarasevich@hotmail.com> wrote in message 
news:j335g1$qs3$1@dont-email.me...
> Default User wrote:
>> If this is still legal, then I'd be on pretty firm ground with my plan 
>> for the actual code.
>
> The code you have in your example is perfectly legal, with perfectly 
> defined behavior.

Ok. That would certainly make the code simpler and cut development time for 
future modules (although of the copy-and-paste variety). Mainly it would aid 
maintenance if any change to the map and call system were needed. Changing 
in one place rather than ten or more would be a good thing.

> Again, beware of broken compilers that might not handle it correctly. But 
> for your specific example I don't expect any surprises from the compiler.

VS 2010 builds the example without complaint. Results are what I would want.

> Keep in mind though, that one restriction imposed on such use of 
> `static_cast` is that the base class should be unambiguous  and 
> non-virtual in the derived class. (I was wrong to say earlier that virtual 
> inheritance in not a problem in this case).

I don't intend to use either of the inheritance types that you mentioned.

Thank you very much for your analysis and comments.



Brian

[toc] | [prev] | [next] | [standalone]


#9295

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-24 09:30 -0700
Message-ID<j338vq$krh$1@dont-email.me>
In reply to#9292
On 8/24/2011 9:15 AM, Default User wrote:
>
>> Again, beware of broken compilers that might not handle it correctly. But
>> for your specific example I don't expect any surprises from the compiler.
>
> VS 2010 builds the example without complaint. Results are what I would want.

Well, VS is actually the problematic one. It has a bunch of settings 
(see '/vms', '/vmm' and '/vmv') intended to control the trade-off 
between compiler compliance and member-function-pointer size/simplicity 
in this specific kind of situation (depending on the inheritance model).

However, somehow they managed to let the Marcel's example slip through. 
When the VMT pointer is introduced into the class layout late in the 
hierarchy (i.e. the base class is not polymorphic, while the derived 
class is), the compiler fails to produce correct code. It is especially 
strange in light of the fact that all the infrastructure that is needed 
to handle this situation correctly is already built into the compiler 
(layout change is similar to that caused by ordinary multiple 
inheritance). Yet that managed to screw this specific case up. It is 
broken in VS 2005. It remains broken is VS 2010.

Of course, one can work around this issue fairly easily by moving the 
VMT pointer all the way up the hierarchy, i.e. by forcefully making the 
base class polymorphic (not even mentioning that it will already be the 
case in [almost] all practical designs). Nevertheless, it is important 
to be aware of it.

-- 
Best regards,
Andrey Tarasevich

[toc] | [prev] | [next] | [standalone]


#9281

FromMarcel Müller <news.5.maazl@spamgourmet.com>
Date2011-08-24 12:27 +0200
Message-ID<4e54d203$0$6629$9b4e6d93@newsspool2.arcor-online.net>
In reply to#9235
Default User wrote:
> "Marcel Müller" <news.5.maazl@spamgourmet.com> wrote in message 
> news:4e5351c3$0$6566$9b4e6d93@newsspool3.arcor-online.net...
> 
>> This is undefined behavior.
> 
> I think that I'm convinced that you're correct. The example I had built 
> without issue and gave me the results I wanted, but as we know UB can do 
> that. Right up until someone changes something and then it doesn't.
> 
>> To illustrate the point: change your code to (see below) and it will sadly 
>> fail.
> 
> You are correct, although adding a virtual destructor to the base class 
> makes it "work" again.

Use multiple inheritance and it will fail again.


>> Member function pointers are not the solution of your problem. You should 
>> use an observer pattern instead.
> 
> I'm not sure how that pattern would apply to this situation. Perhaps I'll 
> start a new topic on the more general question.

The base class should provide an event (or something like that) where 
derived classes could register a handler. The handler should handle the 
operations. It's your choice whether the dispatch table is in the base 
(i.e. one handler for each command) or if the dispatch is in the derived 
class (i.e. each handler catches the operations he can handle and the 
framework tries all handlers until one does the work or it finally fails 
- like a window message procedure).


Marcel

[toc] | [prev] | [next] | [standalone]


#9288

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2011-08-24 06:46 -0700
Message-ID<j32v9g$ggm$1@dont-email.me>
In reply to#9281
Marcel Müller wrote:
>>
>>> To illustrate the point: change your code to (see below) and it will 
>>> sadly fail.
>>
>> You are correct, although adding a virtual destructor to the base 
>> class makes it "work" again.
> 
> Use multiple inheritance and it will fail again.

No. Use a compliant C++ compiler, and it will not fail, regardless of 
the type of inheritance and/or polymorphism :)

GCC, for one example, will handle all these cases correctly.

MSVC++, incidentally, has compiler settings that make it handle multiple 
(and virtual) inheritance correctly, but for some bizarre reason it 
fails on your the original (simple) example regardless of the settings.

-- 
Best regards,
Andrey Tarasevich

[toc] | [prev] | [next] | [standalone]


Page 1 of 2  [1] 2  Next page →

Back to top | Article view | comp.lang.c++


csiph-web