Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #9208 > unrolled thread
| Started by | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| First post | 2011-08-22 16:41 -0500 |
| Last post | 2011-08-25 11:29 -0400 |
| Articles | 20 on this page of 37 — 9 participants |
Back to article view | Back to comp.lang.c++
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 →
| From | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| Date | 2011-08-22 16:41 -0500 |
| Subject | Fun 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]
| From | Victor Bazarov <v.bazarov@comcast.invalid> |
|---|---|
| Date | 2011-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]
| From | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| Date | 2011-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]
| From | Juha Nieminen <nospam@thanks.invalid> |
|---|---|
| Date | 2011-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]
| From | Victor Bazarov <v.bazarov@comcast.invalid> |
|---|---|
| Date | 2011-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]
| From | Juha Nieminen <nospam@thanks.invalid> |
|---|---|
| Date | 2011-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]
| From | Victor Bazarov <v.bazarov@comcast.invalid> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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]
| From | Marcel Müller <news.5.maazl@spamgourmet.com> |
|---|---|
| Date | 2011-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]
| From | Juha Nieminen <nospam@thanks.invalid> |
|---|---|
| Date | 2011-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]
| From | Marcel Müller <news.5.maazl@spamgourmet.com> |
|---|---|
| Date | 2011-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]
| From | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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]
| From | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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]
| From | "Default User" <defaultuserbr@yahoo.com> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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]
| From | Marcel Müller <news.5.maazl@spamgourmet.com> |
|---|---|
| Date | 2011-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]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2011-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