Using handle based smart pointers and boost::shared_ptr

Started by
4 comments, last by Fruny 19 years, 2 months ago
Hello all, I recently read that it would be worthwhile to use a handle-based smart pointers, since this would mean my game entity objects would have unique ID's, which in turn would allow me to much easier saved my games and then reload the game and relink all the pointers. I liked the idea. Expecially since the smart pointers would also prevent memory leaks and dangling pointers. I looked into boost::shared_ptr, since it has built in casting of this smart pointer, but that one uses a reference-counting scheme. So I can use my handles with it. So I am wondering if: 1)there is a smart pointer in the boost library that can use handles or 2) if i should use a different strategy (instead of handles) to accomplish saving and loading of games and the pointers or 3) any other way to combine the two (smart pointers and handles) thank you very much for all the help! Peter
If you aren't diving, you ain't living
Advertisement
1) If your handles are just arbitrary integers for which new and delete have no meaning, then no, there aren't any handle-based smart pointers in the Boost library. There is, however, a policy-based smart pointer framework in the Loki library which would probably let you easily inject a "handle policy".

2) You can save the pointers to the disk. All you have to do is, when you reload, to map those pointers to the new addresses. (Note that if you are using boost's smart pointers, you'll want to use weak pointers between objects; cycles are a bad thing where reference-counted pointers are concerned).

For example:

Object had address 0xAAAA, the value 0xAAAA is written to the disk.
You reload the data, object is now located at 0xBBBB.
Create an entry in a table such as a std::map<size_t, boost::shared_ptr<Object> >
Since you shouldn't be using naked pointers within your objects, you can't leave the old values lying around, so you need to create, at the same time, a second table holding the old values of all those pointers your object contains. It can be the same table as the previous one, if you use a struct as the element value to hold the extra data, instead of only a smart pointer:

struct object_data
{
boost::shared_ptr<Object> address;
size_t data1;
size_t data2;
};

Once all the objects have been loaded (I assume you have a container holding shared_ptr to all of your object, not just an arbitrary graph structure), you can now do a second pass and actually relink them all. Walk down the table (std::map is iterable), and for each 'old address' in the struct, lookup the new address in the table and assign the object's internal pointers.

Once this is done, you can drop the table(s).

I'll grant you, it's probably a bit complicated, it forces you to do two passes over the data, the second one being O(n log n) (since std::map lookups are O(log n) and you have n objects to do lookups for). On the plus side, you are guaranteed not to have dangling pointers (weak_ptr are automatically invalidated if the corresponding shared_ptr are all gone) and in-game access doesn't require any further lookups.

3) Maybe you could just use handles to refer to your objects, but have an in-game table (e.g. again, a std::map, or a (non-standard) hash_map), holding a smart pointer to the object itself. That way, given a handle, you can get access to the object, and yet the object table is itself doing all the memory management for you. Granted, it doesn't really take care of dangling handles... so look at what Loki has to offer. Maybe you'll find what you need in there.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
I don't think you would gain anything at all by using handles, only an additional level of indirection and lots of cumbersome management work. A pointer indeed would serve just as well as a unique id, after all, it's just a 32 bits (or 64 bits number ;)) number that is by definition unique for any instance of a class in a process.

-Markus-
Professional C++ and .NET developer trying to break into indie game development.
Follow my progress: http://blog.nuclex-games.com/ or Twitter - Topics: Ogre3D, Blender, game architecture tips & code snippets.
Quote:Original post by Cygon
I don't think you would gain anything at all by using handles, only an additional level of indirection and lots of cumbersome management work. A pointer indeed would serve just as well as a unique id, after all, it's just a 32 bits (or 64 bits number ;)) number that is by definition unique for any instance of a class in a process.
Yes, but not one that can be persisted across sessions or systems.

Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse

Thank you guys for your feedback, especially Fruny. Your in-depth answer is most valuable.

Admittedly, I have read in my most excellent "C++ for game programmers" book about persisting "raw" (or msart pointers, since u can get @ the raw pointer) pointers and then reloading them with a mapping table and two passes...

I just thought using handles to begin with might be more elegant, even though it introduces the problem that upon creation of my objects, i'd have to create a unique ID (perhaps a GUID) for every single object.

Quote:
Once all the objects have been loaded (I assume you have a container holding shared_ptr to all of your object, not just an arbitrary graph structure)...


I am not sure yet what container to use, since I have to have a closer look at the stl library. But I do know that I for sure need a container for all my objects, since for each frame, I will iterate over all the objects and do their AI, do collision, animattion, etc. The idea is to do everything for the game entity so that i have as few cache misses as possible. So I guess I am looking for a container over which I can easily iterate. For all other purposes, I would use the smart pointer.

Quote:
I'll grant you, it's probably a bit complicated, it forces you to do two passes over the data, the second one being O(n log n) (since std::map lookups are O(log n) and you have n objects to do lookups for). On the plus side, you are guaranteed not to have dangling pointers (weak_ptr are automatically invalidated if the corresponding shared_ptr are all gone) and in-game access doesn't require any further lookups.


I don't think it's too complicated, just have to get the details right. However I do have to look up and gain a deeper understanding of the weak and shared pointers in the boost library. I skimmed over the docs for boost but I can see I need to read up more on it.

Can you think of any other instance where I might need to use UIDs for game entities, besides saving and loading? I think all other times I can use the smart pointer. By the way all this is for a 2D game, as I need to hone my skills more before attempting 3D.

Quote:
3) Maybe you could just use handles to refer to your objects, but have an in-game table (e.g. again, a std::map, or a (non-standard) hash_map), holding a smart pointer to the object itself. That way, given a handle, you can get access to the object, and yet the object table is itself doing all the memory management for you. Granted, it doesn't really take care of dangling handles... so look at what Loki has to offer. Maybe you'll find what you need in there.


How would this not take care of dangling pointers? If you don't find the handle in the table, you assign a null to the pointer. Or am I missing something?

Thanks for all your input, very much appreciated!

Peter
If you aren't diving, you ain't living
Quote:I am not sure yet what container to use, since I have to have a closer look at the stl library.


Depends on what your requirements are. Since you have object IDs, an associative container is probably a good choice. The alternative is to have a container of weak pointers, and have game objects hold shared_ptr to those other objects they contain/are supposed to keep alive. It could be rooted in a "World" object. All you have to make sure is that you do not create cycles...

Quote:I don't think it's too complicated


Well, I wasn't intending to sound condescending, but you did post this in "For Beginners". [smile]

Quote:Can you think of any other instance where I might need to use UIDs for game entities, besides saving and loading?


If your game spans several servers, you'll need to refer to your objects exclusively through their IDs, since you cannot a-priory know whether a given object is on this or another server. Thus, no pointers.

Quote:How would this not take care of dangling pointers? If you don't find the handle in the table, you assign a null to the pointer. Or am I missing something?


Which means that your other objects, which only hold a handle, object ID or whatever, aren't notified that the object has become invalid until they actually try to access it, and you do have to check for nulls. Granted, weak pointers must be checked too, but at least the update is automatic (though you pay for it by having the smart pointer keep track of each and every weak pointer).
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan

This topic is closed to new replies.

Advertisement