• Advertisement

Archived

This topic is now archived and is closed to further replies.

Handles vs. SmartPointers - the discussion ;-)

This topic is 5099 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello! We (i.e. me and a pal) are devoloping a class that wraps up Direct3D (in C++). Though the "problem" I ask you to help us with is more likely a general one, that's why post into this forum. Besides the initialization of Direct3D etc. the class (let's call CRenderer) shall keep track of other objects related to the renderer (e.g. CMeshObject) internally. That is, it shall create an instance of CMeshObject or whatever and adds it (more likely its pointer) to a list or a vector or something. The question actually is how to provide access to these objects in quite an elegant way. We two quite similar ways in mind 1) Handles. All the interaction with the objects in CRenderer is handled through handles (sounds funny *g*). We'd use the handle technique Windows uses as well (for those who don't know: search for #define DELCLARE_HANDLE in the windows includes). An example of using it would be:
// the renderer instance
CRenderer renderer;

// renderer creates a mesh-object and returns the handle that this object is attached to
HMESHOBJECT hMesh = renderer.CreateMeshObject();

// renderer loads geometrie data or whatever to the object attached to hMesh
renderer.LoadMeshData(hMesh, "mesh001.dat");
// renderer actually renders the object attached to hMesh
renderer.RenderMesh(hMesh);
  
It's not that cool, but I think you got it 2) smartpointers. Another way to handle the objects would be using smartpointers. (BTW:Normal pointers would have been too unsafe). Our smartpointer implementation is this one An example code might be:
// the instance of CRenderer
CRenderer renderer;

// renderer creates an CMeshObject instance, adds a smart-ptr to that object into its internal list and returns a copy
// of the smart-ptr (CSmartPtr is only 4Byte so copying has no big performance penalty)

CSmartPtr spMesh = renderer.CreateMeshObject();

// the object loads "its" data
spMesh->LoadData("mesh001.dat");

// CMeshObject implements functions that provide its protected data to the renderer
// e.g. CMeshObject::GetVertexBuffer() or something
renderer.Render(spMesh);
  
Let's compare the pros&cons HANDLE ~~~~~~ pro - no direct (miss-)use of the object is possible con - no direct use of the object is possible (yes, it's also a con...) - the renderer has to implement all the stuff to deal with the objects, i.e. everytime you modify a property of the object, you'll need to do it through the renderer - it's not very OOPish smart-pointers ~~~~~~~~~~~~~~ pro - direct access to the object - more OOPish con - ?? Both methods are safe as the renderer keeps track of the objects internally and destroys them at the end of the app or when needed. Well, I personally like the smart-pointer method best. That's why my comparison chart is... erm... not very neutral. But I'd like to encourage you to tell us your opinion. Which way would you prefer and why? Thanks in advance Bye, VizOne Edited by - VizOne on February 24, 2002 8:29:31 AM

Share this post


Link to post
Share on other sites
Advertisement
I just thought I''d let you know about CComPtr (for COM-based objects).
#include <atlbase.h>
//
...
CComPtr<IDirect3D8> pD3D;
CComPtr<IDirect3DDevice8> pDevice;
...
// this is the only pointer that needs to be attached:
pD3D.p->Attach( ::Direct3DCreate8(...) );
//
// all others can be assigned normally:
pD3D.p->CreateDevice( ..., &(pDevice.p) );

Using this facility you''ll probably have less maintenance to do. Why? Because CComPtr calls Release on the controlled pointer, as well as a few other functions. There''s also CComQIPtr, which supports calling QueryInterface.

[ GDNet Start Here | GDNet Search Tool | GDNet FAQ | MS RTFM [MSDN] | SGI STL Docs | Google! ]
Thanks to Kylotan for the idea!

Share this post


Link to post
Share on other sites
Well, that's not exactly what I intended this thread to be for but thanks a lot. Nice to know about this sucker!

quote:
Original post by Oluseyi

// this is the only pointer that needs to be attached:
pD3D.p->Attach( ::Direct3DCreate8(...) );
//



Not sure, but shouldn't it be pD3D.Attach(...), as Attach is a method of CComPtr and not of IDirect3D8 (or more generally T*)?

Bye, VizOne

Edited by - VizOne on February 25, 2002 2:31:22 AM

Share this post


Link to post
Share on other sites
quote:
Original post by VizOne
Not sure, but shouldn''t it be pD3D.Attach(...), as Attach is a method of CComPtr and not of IDirect3D8 (or more generally T*)?



No.

Share this post


Link to post
Share on other sites
Wow, I am glad someone else brought this topic up. I tried to:

http://www.gamedev.net/community/forums/topic.asp?topic_id=80728

But as you see (if you read it), I did not get any replies.
I have been recently trying the handle method. My ''CRender (as you put it)'' has a Manager for each major resource type (TextureManager, VBManager, etc). These managers are used to create, restore and manage their respective resources.

I have only a couple of worries about my system.
First, I pass a pointer to CRender around to any function that does a graphic type job. Yes, the CRender holds my managers, and it gives a pointer of itself to them when, for example, SetTexture( TextureHandle ) is called, like a passthrough.

Example :

My Entity has a function Render( CRender). The Entity
also has the texture handle it needs, so it will call CRender.SetTexture( m_textHandle );

In turn, the CRender, will just call the Manager..
CRender::SetTexture( textureHandle )
{ TexManager( this, textureHandle ); }

TexManager then finds the resource (currently using an STL map), and gets a DXResource texture pointer. The manager also checks to see if this resource is already setup, reducing search and state changes. As another bonus, if the manager is given a duplicate resource to load, it will not load it twice, and give back the correct handle.

However, besided that, the system works great. The rest of my game engine never worries about the type of VB or texture it uses. Nor does it worry about how to manage them in case surfaces were lost, and what not. Having the CRender class hold the managers means all the DirectGraphics stuff is close together.

So, like you, I have been happy with the design, but worried about the means. Overall, I only have about 2 or 3 passthrough functions per manager, which is not really bad. I was going to create interfaces, but I still wanted to use seperate manager classes. Using handles, and having these managers has really added many pluses to my game architecture as a whole.

Share this post


Link to post
Share on other sites
quote:
Original post by Spiral

No.



No?

The declaration of CComPtr (in atlbase.h) says:


...
void Attach(T* p2)
{
if (p)
p->Release();
p = p2;
}
...
T* p;

(T is the templated type)

@Taulin: your implementation almost exactly matches what we're doing/planning to do. You may overcome the pointer-passing problem in this way: implement CRenderer as a singleton (you'd only want one instance at a time for sure) and give CRenderer a static pointer to its one and only instance ( static CRenderer * m_pInstance; ). Assign "this" to it at creation (in the c'tor or a static creation function). Then provide a function that accesses this pointer or a reference to the instance (static CRenderer & GetInstance(){ ASSERT(m_pInstance); return *m_pInstance} ). With this, you can access the renderer from everywhere without global vars and you don't need passing pointers anymore.



Edited by - VizOne on February 25, 2002 5:07:49 AM

Share this post


Link to post
Share on other sites
quote:
Original post by Spiral
Sorry, i thought Attach() was a D3D method... i should learn to read


no prob :D

Share this post


Link to post
Share on other sites
About the singleton use; I understand what you are getting at, and it seems like a good idea, but I must ask your oppinion on a different standpoint. I do not pass the CRenderer everywhere (almost everywhere since it handles creation of resources and the rest), but not everywhere (like network code, etc). Passing a pointer does not take much time or resources, so that is not a factor. In fact, calling that static accessor may actually be more intensive, if not equal, but again, performance is not the issue here, I do not want to dwell on it.

I would consider seeing the pointer in a function''s prototype/statement better design simply because I know what needs the CRender by simply looking at the header files.

Could you explain why you think using a globally acceesable reference is better than passing a pointer explicitly? One reason I can think of is to cut down on the massive amount of pointer passing, but at that point, better engine architecture is in order.

Share this post


Link to post
Share on other sites
There is really no need for a smart pointer here, is there? What safety does the smart pointer provide over the raw pointer, since all it contains is a reference to some memory managed by the renderer?

What you want is returning a proxy object, which doesn''t require a smart pointer.

I suggest reading "Modern C++ Design" for a understanding (and good implementation) of smart pointers.

Share this post


Link to post
Share on other sites
I''ve used Handles to good effect for everything from vertex buffers to matrices. Probably the worst thing about them is that you CAN fire and forget with them, and have the system delete everything at the end. It is possible to end up with a massive amount of unused, and unfreed memory running around.

Z.

Share this post


Link to post
Share on other sites
I agree about the unused resources. The way my current engine is set up (and projects), I am only worried about scenes with a set number of possible resources, which I load up at the start. So resource management is not a problem. For a game like Dungeon Seiege, I would just add a reference counter (much like COM). Even then, the managers could easily be configured to keep the resource around for a set time period to act as a sort of cache. It is because of this functionality that I wanted them to be seperate from their container, which in this thread is called CRenderer.

Share this post


Link to post
Share on other sites
quote:
Original post by Taulin
In fact, calling that static accessor may actually be more intensive, if not equal, but again, performance is not the issue here, I do not want to dwell on it.


As the accessor is inlined it''s up to the compiler to make it fast...

quote:

I would consider seeing the pointer in a function''s prototype/statement better design simply because I know what needs the CRender by simply looking at the header files.
[/quote
Well, that one''s yours!

[quote]
Could you explain why you think using a globally acceesable reference is better than passing a pointer explicitly?


I see two reasons (well, it were three but I forgot one *g*):
1) You have to store the pointer to the instance somewhere. Well, that''s not actually a problem, but I like singletons to carry their instance themselves.
2) I like to assert pointers in my debug builds, and with the static accessor I can to that at one centralized place (i.e. the accessor function itself) and don''t have to do it in every function that uses it.

quote:
Original post by Void
There is really no need for a smart pointer here, is there? What safety does the smart pointer provide over the raw pointer, since all it contains is a reference to some memory managed by the renderer?


Again, mainly two reasons:
1) A pointer may be tried to deleted *ouch*, that would be bad, wouldn''t it? The object the smart pointer is attached to can''t be deleted directly through the smart pointer (of course, it''s deleted when no sp points to it anymore)
2) More important: Objects stay valid even if they aren''t managed by the renderer anymore: let''s say you have a meshobject with a texture the meshobject itself is managed by the renderer, and the texture within it, too (so the texture may be shared among several mesh-objects). All the lists of managed objects in the renderer are smart ptrs as well. Now you (some kind of) flush the texture list of the renderer. This will delete all textures, that are not longer used. But all textures that *are* used (i.e. for which the refcount is >0) are still valid. And that''s what I want.

quote:

What you want is returning a proxy object, which doesn''t require a smart pointer.


I didn''t understand that exactly. Could you explain?

quote:

I suggest reading "Modern C++ Design" for a understanding (and good implementation) of smart pointers.

Is my implementation that bad Besides I think I got the point of smart pointers :D Nevertheless the book might be worth a look at.

Bye, VizOne

Share this post


Link to post
Share on other sites
Even the smart pointer approach isn''t very OO.

As you can''t extend any of the classes unless you can modify the CRenderer class to add in extra:

CRenderer::CreateMesh(X)Object functions etc.

A better way IMO is to just have an AddMesh, which takes into it a pointer to a base class Mesh. Then let the calling "Module" be responsible for memory management.

This doesn''t prevent you from reusing a resource it just means that when you do your final remove: The calling module is told it''s reference count is 0, and therefore CAN/SHOULD be deleted.

That''s offcourse if you want it to be OO, the way you have it, it''s more object based (which isn''t necessarily wors).

Also the pro you had for the handle approach is not relevant as either or ANY methodology should not allow "direct (miss-)use of the object". But I guess you where just clutching at straws for a reason to use the Handle way. Which is really only necessary in the C environment, which requires a means to hide information in the structs, when using C++ just use private/protected members.

Share this post


Link to post
Share on other sites
i use a similar approach to what has been described here for managing my resources (load for first request, storing identifier in an STL map, and any subsequent requests for that resource are given a reference to the one previously loaded... i use reference counting to handle cleanup)

the way i generally get around the problem of the object being tampered with via the reference handed out is to simply pass it back as a const reference... then the other object cannot alter its state.

when an object is done with a resource (ie being destroyed), it must release it through the resource manager (and even though the resource may not actually be deleted, i treat it as such once release has been called).

Share this post


Link to post
Share on other sites
quote:

2) I like to assert pointers in my debug builds, and with the static accessor I can to that at one centralized place (i.e. the accessor function itself) and don't have to do it in every function that uses it.



Yes, that does sound good. It is a fact, every function I pass my Renderer to, I check it for NULL. But this is where it comes down to personal preference. Once again, I must defend having the pointer or reference being passed in so I know what functions need it. Those two lines of code really do not take that long to copy and paste.

quote:

the way i generally get around the problem of the object being tampered with via the reference handed out is to simply pass it back as a const reference... then the other object cannot alter its state.



Yes, using const everywhere is good practice. It not only helps the compiler in some cases, it increases good coding habits, and stops people from doing stupid things.

EDIT: Got the quotes working.



Edited by - taulin on February 26, 2002 8:35:00 PM

Share this post


Link to post
Share on other sites
quote:
Original post by VizOne
I didn''t understand that exactly. Could you explain?
[quote]

You are trying to do reference counting for the object handle you passed out only. You don''t need it to use smart pointers for the internal storage.

There are many approaches to handling this. I would go for the cleaner approach where the renderer would keep track of the reference count, rather than through the resource accessor.

Share this post


Link to post
Share on other sites
After playing with the implementation a bit, I decided not to use smart pointers. As it has come clear to me that I need access to details of the objects (meshes, texture) nowwhere but *inside* the renderer class itself, I''ll you some kind of simple handle technique or - more low-level - simply pointers.


@Void: /me thinks you''re quite right. I''ll try a cleaner way.


@Taulin: of course "I" created the pointer and should deal with it correctly. Nevertheless I like the Idea of having the instance pointer within the instanciated object (mainly for a simple singleton implementation - the class should know, if it was instanciated once). BTW: as I''m not the only one that''ll use the lib. The renderer is a part of a complete game-framework, using some techniques of the MFC but being specialized for gamedev. It''s quite modular, so you only have to link to the parts you need. Some modules are app and window, input (DInput8), network (DPlay8), gfx (D3D8), sound (DMusic/DSound8), a virtual file-system (with archive support) ans so on (detail on my HP (only German now...). You got the point *g*. That''s why I want some sorta safety. And you know what they say: trust no one!


@d9930380:
quote:

As you can''t extend any of the classes unless you can modify the CRenderer class to add in extra:


as the lib (see above) can be extended the way you need, that''s exactly what I want: the programmer using the lib *will* extend the renderer class to his needs.

Bye, VizOne

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Well... im knee-deep in my first project and i just recently read this article titled: "A Generic Handle-Based Resource Manager" from here

http://www.drizzle.com/~scottb/publish/gpgems1_fubi.htm

The template approach is unbeatable, just solved my dependency problem with resource classes and a separate renderer class & sound class. The example there uses handles, and i thought about changing it back to pointers... and the invalid reference problem shows up (an object erroneously tries to use an already fred pointer), so then i thought about using const pointers, but the invalid references problem would still be there.

Then i thought about const proxy pointers... and keeping a list of used pointers... and then all the overhead is really similar to handles(except for a few extra stack calls). So... i guess i''ll go with handles.

so im going with a templatized resource manager, instantiated in each class (example, texture manager is instantiated by the renderer), with handles, reference counting and name mapping.

GDnewb::Madster

Share this post


Link to post
Share on other sites
I''ve been reading through here, and I don''t recall anyone mentioning a nice benefit of handles; they allow you to save information to a file, while a simple pointer does not. ie; A texture manager should have the same handle to each texture each time it''s loaded. If you want to save some game data to a file (ie; what texture an orc has), then it''s as simple as saving the handle.

In any case, smart pointers should still be used. You will eventually pass a pointer to the caller w/ the id of the managed object, and that pointer should be a smart pointer IMO. The risk of dangling pointers is just too great, and smart pointers solve quite a few memory leak problems. Not to mention, they make pointer problems much easier to debug.

Just download boost (search google, but I think the URL is www.boost.org), if nothing else for their SmartPtr class.

Share this post


Link to post
Share on other sites

  • Advertisement