Thread: RE: [GD-General] Multiple Inheritance and RTTI
Brought to you by:
vexxed72
From: Gareth L. <GL...@cl...> - 2003-02-04 11:18:22
|
Personally I find COM very good at exactly what you want. You would have to "reimplement" COM. Which is good, because you don't need a lot of what it gives you, and you can streamline it for your own means. I would recommend reading Inside COM, it's a very good introduction and tries to teach you from the bottom up. RTTI is fairly fast in my tests on MSVC. But I've never tried GNU |
From: Wayne C. <wc...@re...> - 2003-02-04 11:23:28
|
> Some say it's lightning fast, others that it's appallingly slow. > I've never used it heavily enough to notice a hit. This depends on how you look at it and how much you're using it :) Compared to, say, just copying a pointer it is appallingly slow and if it's used a considerable amount the costs can begin to mount up. It's the RTTI that generally bloats code, all the type information has to go somewhere. Although if you start deriving from virtual base classes that can add a bit more (I've seen implementations where the processor spent a *lot* of time inside the RTTI\dynamic_cast code). Just make sure you profile regularly and have backup plans if it goes wrong. > The next most simple approach seems to be for the Entity base class to > provide a virtual accessor function for each interface, which returns NULL > by default but can be overriden by a derived class: > > One major drawback here seems to be that the vtable for each Entity class > will be bloated by over a hundred bytes, which seems like a bad move for > cache limited situations. I quite like this method, I'm not so worried by the vtable size, though. But more worried by the fact that you're limited to the supplied methods. So if you add a new class and it needs to be supported, then it needs to be added to the base class (meaning a *huge* recompile). Which limits the extensibility of the implementation. > Finally, the Entity base class could provide a single virtual accessor > function that takes an interface identifier as a parameter: This is the method I currently use, it's also how COM works. The only change from your example is that I pass in a pointer-to-a-pointer and the call returns a success\failure result. Eg IRenderable *pRenderable; If ( entity->QueryInterface( IID_IRenderable, ( void** )&pRenderable ) ) pRenderable->Render(); I usually implement the function as conditional code and to avoid the implementation pitfalls I use some macros to make it easy to implement. QueryInterface on the implementation side looks like: IMPLEMENT_CLASS( MyRenderableEntity ) IMPLEMENTS_INTERFACE( IEntity ) IMPLEMENTS_INTERFACE( IRenderable ) END_CLASS() A more in-depth description of this can be found in my (currently incomplete) SDK documentation located at http://www.btinternet.com/~nfactorial/MantraSDK.zip. Wayney -Virus scanned and cleared ok |
From: Alex L. <ali...@au...> - 2003-02-04 23:33:14
|
Has anyone considered comparing the vtable pointers themselves in order to identify a class? As far as I understand it, every polymorphic class is going to have a unique vtable pointer, and while it's compiler dependent, my experience is that the vtable pointer tends to be the first "member" data in every instance. i.e. int vtable = *reinterpret_cast<int*>(someInstancePointer); I'm not too sure about multiple inheritance. One problem is that you need an instance of every class you wish to identify so that you have a value to compare! Some start up function could harvest these from temporary instances. I would love to hear problems with this! Alex Lindsay |
From: Thatcher U. <tu...@tu...> - 2003-02-05 00:14:29
|
On Feb 05, 2003 at 10:33 +1100, Alex Lindsay wrote: > Has anyone considered comparing the vtable pointers themselves in > order to identify a class? > > [...] > > I would love to hear problems with this! Well, one problem is that it identifies a class, but it doesn't cast for you, so it's not really solving the original problem. -- Thatcher Ulrich http://tulrich.com |
From: Peter D. <pd...@mm...> - 2003-02-05 11:59:03
|
Alex Lindsay wrote: > Has anyone considered comparing the vtable pointers themselves in > order to identify a class? > > As far as I understand it, every polymorphic class is going to have a > unique vtable pointer, and while it's compiler dependent, my > experience is that the vtable pointer tends to be the first "member" > data in every instance. > > i.e. int vtable = *reinterpret_cast<int*>(someInstancePointer); > > I'm not too sure about multiple inheritance. It depends on your compiler. The MSVC object model always places the vptr at offset 0. struct X { int x; }; struct Y: public X { virtual ~Y(); }; On MSVC, Y::__vptr is at offset 0, and the X subobject is at offset 4. Note that this means that if you put Y* into a void* and then get X* from that void*, you'll be surprised. And there's no MI in sight. Other compilers, on the other hand, prefer to leave X at offset 0, and Y::__vptr goes at offset 4. Another interesting tidbit is that the vtable is not guaranteed to be unique. Even on MSVC, where the linker is very good at merging equal things, you could have one vtbl in the EXE and one in the DLL. The Windows loader isn't that good yet. This applies to &typeid(x), too. |
From: <phi...@pl...> - 2003-02-05 19:36:02
|
Alex Lindsay <ali...@au...>: > As far as I understand it, every polymorphic class is going to have a unique vtable pointer, and while it's compiler dependent, my experience is that the vtable pointer tends to be the first "member" data in every instance. DANGER! This assumption is not true for gcc, unless all of the classes you're making this assumption for have a virtual function in their root base class. For gcc, at least the versions I'm familiar with, the vtable is the first item in the first class in the heirarchy with a virtual function. To take Peter's example: struct X { int x; }; struct Y: public X { virtual ~Y(); }; In gcc, the vtable would be the second member in raw memory. Other than that, it's a nice, if not particularly portable, idea. Personally I'd like to have formal access to the vtable pointer (and the corresponding labels exposed), so you could do precisely this sort of 'quick and dirty' hand rolled RTTI. The standard RTTI scheme is, IMHO a bit, all or nothing, and with all being expensive overkill in many cases, nothing is what you usually get. Cheers, Phil |
From: <phi...@pl...> - 2003-02-05 19:36:36
|
> Personally I find COM very good at exactly what you want. Except for the cross platform thing. Cheers, Phil |
From: Nicolas R. <nic...@fr...> - 2003-02-06 01:55:28
|
Well, I always managed to compile the simplest (and yet most interesting) part of COM on any compiler I have ever found for ages (like CW3 on MAC). struct IUnknown { virtual HRESULT QueryInterface(const GUID& id, void** ppOut) = 0; virtual HRESULT AddRef(void) = 0; virtual HRESULT Release(void) = 0; }; Note that appart from the "GUID" and the "HRESULT" (that can be replaced by virtually anything, though me think the windows declaration is good enough), there is absolutely NO specific platform thing... Of course, you wont have the threading model, the CoCreateInstance stuf, and so on... But I really feel that for a game that should not be too annoying, especially if you already have a set of factories for your entities, a CreateInstance class is not too difficult to implement. Nicolas Romantzoff. -----Original Message----- From: gam...@li... [mailto:gam...@li...] On Behalf Of phi...@pl... Except for the cross platform thing. Cheers, Phil |
From: <phi...@pl...> - 2003-02-05 19:41:50
|
>brian sharon <pud...@po...> wrote: >> Question: there were references to code bloat produced by RTTI. Is thatreally a big issue? It's just some bytes added per vtable - I'm wondering if anyone's measured it to come up with a more exact number than that. Noel Llopis <ll...@co...>: > You are right, it is probably not all that significant. I haven't measured it, and it can vary from implementation to implementation, but it's not going to amount to a lot of memory. The main drawback, on the PS2 at least, is that the RTTI information is in a seperate block, pointed to by an extra vtable member. This incurs an extra potential data cache miss every time the RTTI is accessed, and data cache misses are bad. Cheers, Phil |
From: Gareth L. <GL...@cl...> - 2003-02-05 20:47:45
|
> > Personally I find COM very good at exactly what you want. > > Except for the cross platform thing. I specifically addressed that issue. COM is an idea not a tool. It can be implemented on any platform. and as I said in my original post, reimplementing the basics is a Good Thing because there is a lot ( Like using the registry to find objects ) that you don't need from COM. |
From: Peter D. <pd...@mm...> - 2003-02-05 20:54:00
|
Gareth Lewin wrote: >>> Personally I find COM very good at exactly what you want. >> >> Except for the cross platform thing. > > I specifically addressed that issue. COM is an idea not a tool. It > can be implemented on any platform. and as I said in my original post, > reimplementing the basics is a Good Thing because there is a lot ( > Like using the registry to find objects ) that you don't need from > COM. Out of curiosity, can someone post a real example of using the QueryInterface pattern (in performance-critical code)? |
From: Noel L. <ll...@co...> - 2003-02-05 21:07:40
|
On Wed, 05 Feb 2003 22:53:49 +0200 Peter Dimov <pd...@mm...> wrote: > Out of curiosity, can someone post a real example of using the > QueryInterface pattern (in performance-critical code)? Sure, we use it in MechAssault. Every frame we quickly cull out any scene nodes that are not visible from a camera, and then for each scene node we query to see if they implement the IRenderable interface. If they do, they get a render call. Performance was never a problem in our case; It never even showed up anywhere in a profiler report. --Noel ll...@co... |
From: Peter D. <pd...@mm...> - 2003-02-05 21:25:01
|
Noel Llopis wrote: > On Wed, 05 Feb 2003 22:53:49 +0200 > Peter Dimov <pd...@mm...> wrote: > >> Out of curiosity, can someone post a real example of using the >> QueryInterface pattern (in performance-critical code)? > > Sure, we use it in MechAssault. > > Every frame we quickly cull out any scene nodes that are not visible > from a camera, and then for each scene node we query to see if they > implement the IRenderable interface. If they do, they get a render > call. Yes, I envisioned something like that. What are the advantages of this approach over just providing a (possibly empty) render() in every node type? A virtual call can never lose to a QI + test + potential call. |
From: Noel L. <ll...@co...> - 2003-02-05 21:38:05
|
On Wed, 05 Feb 2003 23:24:54 +0200 Peter Dimov <pd...@mm...> wrote: > > Every frame we quickly cull out any scene nodes that are not visible > > from a camera, and then for each scene node we query to see if they > > implement the IRenderable interface. If they do, they get a render > > call. > > Yes, I envisioned something like that. > > What are the advantages of this approach over just providing a (possibly > empty) render() in every node type? A virtual call can never lose to a QI + > test + potential call. Clearly, it's not there for performance reasons. The main thing it buys us is some clarity (and maintainability) by keeping the interface of each type of scene node to a minimum size. Especially if you use many potentially different interfaces, things could get pretty cluttered. It also makes it easier to create new types of objects. If I create a new scene node I might not be sure exactly what functions I need to override so it renders correctly. As soon as I inherit from IRenderable things are very clear (and if they aren't, the compiler will remind me to implement all those pure virtual functions). In our case we ended up not having as many different types of scene nodes or as many different interfaes as I initially anticipated, so we could very well have folded the few functions in IRenderable back into the SceneNode class. I think that another potential advantage is that the QueryInterface method might work better in a very heterogeneous environment, where you are manipulating objects of all sorts of types, without a common base class. Requiring that they all implement all the possible common functions would be a total pain. But I haven't seen that in practice. --Noel ll...@co... |
From: gekido <mi...@ub...> - 2003-02-05 21:35:02
|
with this said, anyone know of any opensource projects implementing a base COM-style cross-platform & cross-compiler library out there? surely there must be someone that's done the hard part for us ;} just curious mike w www.uber-geek.ca > > > Personally I find COM very good at exactly what you want. > > > > Except for the cross platform thing. > > I specifically addressed that issue. COM is an idea not a tool. It can be > implemented on any platform. and as I said in my original post, > reimplementing the basics is a Good Thing because there is a lot ( Like > using the registry to find objects ) that you don't need from COM. |
From: <cas...@ya...> - 2003-02-05 22:31:59
|
gekido wrote: > with this said, anyone know of any opensource projects implementing a base > COM-style cross-platform & cross-compiler library out there? > > surely there must be someone that's done the hard part for us ;} You may want to have a look at mozilla XPCOM. It's not hard to implement it yourself, though. Ignacio Castaño cas...@ya... ___________________________________________________ Yahoo! Móviles Personaliza tu móvil con tu logo y melodía favorito en http://moviles.yahoo.es |
From: Jamie F. <ja...@qu...> - 2003-02-06 12:00:09
|
indeed; we use a cross platform version of COM in our engine. not very helpful link: http://www.qubesoft.com/docs/q-docs/qcom/faq.html jamie -----Original Message----- From: gam...@li... [mailto:gam...@li...]On Behalf Of Gareth Lewin Sent: 05 February 2003 20:48 To: gam...@li... Subject: RE: [GD-General] Multiple Inheritance and RTTI > > Personally I find COM very good at exactly what you want. > > Except for the cross platform thing. I specifically addressed that issue. COM is an idea not a tool. It can be implemented on any platform. and as I said in my original post, reimplementing the basics is a Good Thing because there is a lot ( Like using the registry to find objects ) that you don't need from COM. ------------------------------------------------------- This SF.NET email is sponsored by: SourceForge Enterprise Edition + IBM + LinuxWorld = Something 2 See! http://www.vasoftware.com _______________________________________________ Gamedevlists-general mailing list Gam...@li... https://lists.sourceforge.net/lists/listinfo/gamedevlists-general Archives: http://sourceforge.net/mailarchive/forum.php?forum_id=557 |
From: <phi...@pl...> - 2003-02-05 21:16:14
|
>>> Personally I find COM very good at exactly what you want. >> Except for the cross platform thing. >I specifically addressed that issue. COM is an idea not a tool. Ooops, apologies for jerking that knee a little too quickly... Cheers, Phil |
From: Jay W. <woo...@Ro...> - 2003-02-06 01:00:37
|
With a single virtual call to a parameterless Render function, there's = not an advantage. But in other situations and/or for other reasons, a = QueryInterface style does have advantages: 1) When you want to call multiple functions offered by a single = interface, you can query once, test once, then call many times with = impugnity. That's quite possibly more efficient than calling a bunch of = potentially-empty virtual stubs. 2) Using a QueryInterface style allows you to avoid making the function = call entirely. If you use many virtual functions in place of an = interface, and if the functions take many parameters, you'll still have = to spend time pushing those parameters onto the stack, even if the = function turns out to be just an empty stub. (Compiler gurus please = correct or clarify, but I'm sure this is usually true.) 3) At development time, you won't have to alter the virtual base class = every time you want to add new functionality. > -----Original Message----- > From: Peter Dimov [mailto:pd...@mm...] > Sent: Wednesday, February 05, 2003 1:25 PM > To: gam...@li... > Subject: Re: [GD-General] Multiple Inheritance and RTTI > > What are the advantages of this approach over just providing=20 > a (possibly empty) render() in every node type? A virtual call can = never=20 > lose to a QI + test + potential call. |
From: Gareth L. <GL...@cl...> - 2003-02-06 10:26:40
|
> Out of curiosity, can someone post a real example of using the > QueryInterface pattern (in performance-critical code)? QueryInterface is fairly quick. A lot depends on the number of interfaces you are implementing per object. But most things use macros like (note, this is psuedo code.) START_INTERFACELIST ADD_INTERFACE(IID_IRender) ADD_INTERFACE(IID_IFoo) END_INTERFACELIST which becomes somethinglike bool QueryInterface(GUID iid, void** ppObject) { if (iid == IID_IRender) { (*ppObject) = reinterpret_cast<void*>(this); return true; } if (iid == IID_IFoo) { (*ppObject) = reinterpret_cast<void*>(this); return true; } (*ppObject)=0; return false; } GUIDs can be anything. You could go the extreme "time+network card" which is overkill. FourCCs work very well as well. As long as you make sure not to clash. Depends on the size of the team I guess. If you have a lot of interfaces per concrete class you might need a diff solution. |
From: Peter D. <pd...@mm...> - 2003-02-06 12:48:29
|
Gareth Lewin wrote: >> Out of curiosity, can someone post a real example of using the >> QueryInterface pattern (in performance-critical code)? > > QueryInterface is fairly quick. A lot depends on the number of > interfaces you are implementing per object. > > But most things use macros like > > (note, this is psuedo code.) > > START_INTERFACELIST > ADD_INTERFACE(IID_IRender) > ADD_INTERFACE(IID_IFoo) > END_INTERFACELIST > > which becomes somethinglike > > bool QueryInterface(GUID iid, void** ppObject) > { > if (iid == IID_IRender) > { > (*ppObject) = reinterpret_cast<void*>(this); > return true; [...] Thanks. :-) But I asked for a real example (read taken from a real project) of using (not implementing) the QI pattern, an example that demonstrates the superiority of QI over the alternatives. FWIW, for the QI implementation, I prefer a dynamic_cast-style interface: if(IRenderable * p = queryInterface<IRenderable>(q)) { // do stuff } (when I feel like reinventing dynamic_cast at all, that is.) |
From: Gareth L. <GL...@cl...> - 2003-02-06 10:28:41
|
> > > Every frame we quickly cull out any scene nodes that are not visible > > from a camera, and then for each scene node we query to see if they > > implement the IRenderable interface. If they do, they get a render > > call. > > Yes, I envisioned something like that. > > What are the advantages of this approach over just providing > a (possibly > empty) render() in every node type? A virtual call can never > lose to a QI + > test + potential call. It depends on the number of functions you have in the base class. If it's just Render then sure, that's fine. But if you have 4 or 5 functions in each interface and say 20 interfaces in the system ( with an average of 2-3 per concrete object ) that would end up as 100 pure virtual functions, which starts to get messy. |
From: Gareth L. <GL...@cl...> - 2003-02-06 10:31:13
|
COM basics are extremly simple to implement. You really need a standard way to implement a "Give me this interface" function. (QueryIterface) A standard way of iding interfaces (GUIDs or FourCCs or whatever) And a base interface that concreate class implements. (IUnknown) A lot of COM also uses Factories, Ref counting and sometimes smart pointers. They are not needed, but tend to fit well ( For example ref counting works nice with QueryInterface because you might have a few interfaces to the same object ). Again, Inside COM is a must read if you plan on having a COM like system. > -----Original Message----- > From: gekido [mailto:mi...@ub...] > Sent: 05 February 2003 21:30 > To: gam...@li... > Subject: Re: [GD-General] Multiple Inheritance and RTTI > > > with this said, anyone know of any opensource projects > implementing a base > COM-style cross-platform & cross-compiler library out there? > > surely there must be someone that's done the hard part for us ;} > > just curious > > mike w > www.uber-geek.ca |
From: Thatcher U. <tu...@tu...> - 2003-02-06 14:16:09
|
On Feb 06, 2003 at 10:31 -0000, Gareth Lewin wrote: > COM basics are extremly simple to implement. > > You really need a standard way to implement a "Give me this interface" > function. (QueryIterface) > A standard way of iding interfaces (GUIDs or FourCCs or whatever) > And a base interface that concreate class implements. (IUnknown) To try to cut through the terminology a little: COM == RTTI QueryInterface == dynamic_cast GUID == typeid IUnknown == any polymorphic class Obviously the details vary. -- Thatcher Ulrich http://tulrich.com |
From: brian s. <pud...@po...> - 2003-02-06 17:55:45
|
I think that might be too much of a simplification. The ability to dynamic_cast implies object identity. If I can dynamic_cast from InterfaceA * to InterfaceB *, I know that the pointer I'm holding points to an object that is both an InterfaceA * and a InterfaceB *. QueryInterface doesn't mandate that. If I query for InterfaceB* from an InterfaceA * pointer, I might get back a pointer that points to the same object as the InterfaceA *, like dynamic_cast would return. But I could very well get back a pointer to a completely different object; the object pointed to by InterfaceA * could just as well return a pointer to a InterfaceB* member (see my previous post re: aggregation). Why might I want to do that? A few reasons: 1) I can inherit the behavior of an InterfaceB implementation without having that implementation exposed to me at all. All I need is the definition of InterfaceB and a factory function that can create an object to implement InterfaceB for me. 2) I might want to delay creating an InterfaceB object until it's needed, if I know that it's heavyweight. All that COM requires of QueryInterface is (1) consistency: if you can get an interface once, you can always get it and (2) identity: if you can get an InterfaceB * from an InterfaceA *, you can go back to the InterfaceA * from the InterfaceB *. dynamic_cast goes further and implies an "is-a" relationship between InterfaceA and InterfaceB. --brian On Thursday, February 6, 2003, at 06:12 AM, Thatcher Ulrich wrote: > To try to cut through the terminology a little: > > COM == RTTI |