Now I remember why I don't like C++
I have refactored IUP/Qt into a lovely object hierarchy. And I now remember why I dislike C++.
Here's the basic problem (code may be wrong, I don´t have the broken versions anymore):
I have a base class IObject, that has stub setters/getters for all the IUP attributes.
For each kind of widget, I create a class that inherits from IObject and the matching Qt widget (example: QDialog).
These have to be hooked into an Ihandle structure through the use of a void * (remember, that's C, I have that too ;-)
Now, this was my first naive approach:
Ihandle *n; IQDialog *d=new IQDialog(); n->handle=d; //handle is a void*
That compiles ok. However, when you try to call a d member later (and I know I am using old C casts... they had worked for me until today!) ...
((IObject *)(n->handle))set_title("Title here");
Blammo, segfault. Why? Because in one of the assignments to/from void *, the pointer for some reason starts to point elsewhere.
Here´s what Gliptic at #C++ suggested, and it does work:
Ihandle *n; IQDialog *d=new IQDialog(); n->handle=static_cast <void *>(static_cast < IObject *> (d));
Right, I have to cast it twice.
And here is how you get it back:
(static_cast < IObject * >(n->handle))->set_title ("Some title");
Isn't this just ridiculously weird for anyone coming from any other language?
Or am I missing something completely?
But the good news is, it works ok, and the Qt backend now has a decent structure to hack on.
This is not a fault of C++. The problem arises because you use the C idiom to store untyped objects in void * variables.
In any sane language and also in C++ your n->handle would be of type or interface IObject * and then you can write:
n->handle = d;
eh.. perhaps you should initialize n to point somewhere? (or did you just drop that line of code from your example?)
Ponto: I have to do that because that piece of the code **is** C.
The goal is interfacing with that C library. So, if I start converting it to C++, it kinda defeats the point, doesn't it? ;-)
From every other language, when you convert to a void* and back, you get the same thing. And I had never seen a need to cast twice before :-P
AC: dropped of the example.
if your IQDialog inherit FIRST from QDialog and then from IObject, when you do :
you put a pointer to a QDialog object in d !
and then, when you get it, you cast a pointer to a QDialog in a IObject, and than, you method call fail with a segfault.
for that raison, if you cast it to a IObject when you put it in n->handle, the QDialog part of the object is skipped and then you get a "true" IObject.
I think the solution in your case is to change the inheritance order in your objects. First inherit from IObject and then from the QT widget.
Remember, multiple inheritance can create more problem that it solve !
bced: that would only cause trouble in other places where I need to access the "qdialogness" of a iqdialog :-)
Anyway, I am happy I got through this thanks to the great help of #C++ but it got me really frustrated for a while.
I mean, I never expected casting to change where the pointer points to. That's of course due to my ignorance of C++.
It's weird, what you describe. If you cast a SomeObject* to AnyPtr*, nothing is ever lost. Casting to SomeObject* back brings the whole thing. C included.
Except if you haven't initialized n :S ..
Furthermore, trying to dynamic_cast(anyptr), will (at some expense) verify that the pointer really holds the right thing.
If you have an object with multiple interfaces, when you cast to same of the interfaces the compiler must adjust the offset of the pointer.
class A: public B, C
B* pb = &a;
C* pc = &a;
Then may result that pb is not the same pointer as pc. The offset depend if B hast data members, in first place came B data members then C data members.
pc = static_cast(pb);
This can fail because the compiler can't deduce the right offset for C* because B y C are not related (only trough A).
To correct this you need the double cast.
Anonymous: that sounds like a reasonable explanation for the phenomenon, but doesn't it mean that we are working around an implementation issue?
After all, there is no reason why the casting should be done by changing the pointer to another location, or that the multiple inheritance should be implemented by having one interface for each ancestor.
An object could have a single interface with the combined interfaces of all ancestors.
In such an implementation, the naive approach would work, and it would all be much simpler.
Unless of course the standard dictates how an object must be laid out in memory, in which case I say the standard is way too detailed ;-)
It's not that simple. Consider again the previous poster's example.
Suppose you're interfacing with a library that has been compiled already, and that library *only knows about class C*. When you pass a C* to that library, the pointer *must* point to the beginning of the C part of your A object.
Now, if the C part of your A object happens to be the first part of the A object, then everything will work fine even with C style casts. This is why you'll never have problems without multiple inheritance.
But with multiple inheritance, it becomes obvious that both ancestors cannot be the first part of an A object at the same time - one of the ancestors needs to be moved into the interior of the A object. And in that case, the pointer must change, there's simply no way around it.
So there really is no way around the explicit C++ style casts when converting to/from void*.
Well, there would be if C++ didn't rely on a simple vtable to find the methods. Ok, that would hurt performance, I supppose.
this is really interesting viewpoint on the subject i might add
Well, the write-up is truly the freshest on this laudable topic.