Sign in to follow this  

Smart pointers question

This topic is 2078 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,

I can clearly see the benefits of using smartpointers in complex software like game engines. However there is one thing that really makes me confused.
The whole idea of smart pointers is the reference counter. I have a ObjectFactory which constructs and encapsulates a user-provided class in a GameObject template. The template also gets assigned an Id of the object, it uses custom allocation/deallocation, etc



[code]template <class T>
class Object
{
public:
inline Object(IMemoryManager* memMgr)
{
Init();
if (memMgr)
{
mMemoryMgr = memMgr; mObj = (T*) mMemoryMgr->Allocate(sizeof(T));
mRefCount = 1;
}
}
inline Object(bool UseMemPool, IMemoryManager* memMgr)
{
Init(); if (memMgr)
{
mMemoryMgr = memMgr;
mObj = (T*) mMemoryMgr->Allocate(sizeof(T),UseMemPool);
mRefCount = 1;
mUseMemoryPool = UseMemPool;
}
} inline Object(Object<T>& ptr)
{
Init();
mObj = ptr.mObj;
mRefCount = ptr.mRefCount;
mUseMemoryPool = ptr.mUseMemoryPool;
mMemoryMgr = ptr.mMemoryMgr;
IncRef();
}
inline ~Object()
{
if (mObj && mMemoryMgr)
{
DecRef();
if (mRefCount <= 0)
{
mMemoryMgr->Deallocate(mObj,mUseMemoryPool);
}
mObj = NULL;
}
}
//......
[/code]



The ObjectFactory keeps its constructed gameobjects in a map, because at engine tick() it proceeds to call Draw() / Update() on each gameobject. Which means there should always be a reference to that object in the objectfactorys map, right?

And thats what I dont understand, or I probably designed it wrong. Since there is always a reference back in the ObjectFactory (an engine-component that dosn't get destroyed untill the engine stops) no memory will ever be freed untill the engine stops.

And even if memory would get freed outside of the ObjectFactory, there would be a dangling pointer in the ObjectFactory?

That's what makes simple handles seem ideal to me, although of course they don't auto-delete themselves.

Can anyone explain how this is supposed to work?

Thanks

Share this post


Link to post
Share on other sites
First off, not all smart pointers use reference counting, for example see std::auto_ptr and std::unique_ptr. To solve your specific problem, reference counting smart pointers often support weak pointers. For example boost::shared_ptr has boost::weak_ptr, which stores a reference to a smart pointer managed object but does not increment the reference count itself. If all the shared_ptrs to an object are destroyed then the object is destroyed.

Share this post


Link to post
Share on other sites
[quote name='KaiserJohan' timestamp='1333057744' post='4926460']
The ObjectFactory keeps its constructed gameobjects in a map, because at engine tick() it proceeds to call Draw() / Update() on each gameobject. Which means there should always be a reference to that object in the objectfactorys map, right?

And thats what I dont understand, or I probably designed it wrong. Since there is always a reference back in the ObjectFactory (an engine-component that dosn't get destroyed untill the engine stops) no memory will ever be freed untill the engine stops.

And even if memory would get freed outside of the ObjectFactory, there would be a dangling pointer in the ObjectFactory?
[/quote]

The standard use for reference counted smart pointers is for objects don't have an owner / manager that can manage their lifetime; objects that don't otherwise have rules about construction and destruction besides "exist while something is using you". In the case of game objects, they should have game specific rules about when they get destroyed (when they get killed, when they go offscreen, when the player picks up the powerup, etc).

Textures and meshes are what usually fit this. I personally don't like the standard ref counted smart pointer, which will get auto deleted when not used. I'd use ref counted smart pointers to mark objects as "not in use, delete if you need memory".

Note that this doesn't mean you [b]can't [/b]use smart pointers for game objects, you just have to do one of two things: the keep a plain pointer to the object in the ObjectFactory, and have the game object's destructor tell it when it gets destroyed, or keep a smart pointer in the ObjectFactory and assume that the object is not in use when its ref count is 1 rather than 0 (because that would mean that only the ObjectFactory has a pointer to it).

Share this post


Link to post
Share on other sites
Would it be better to use a Handle-like system for GameObjects in the ObjectFactory then?

Basically have a single look-up table to see if the item is not deleted everytime you use it, like overriding the '->' operator. That way the ObjectFactory could still track the Gameobjects.

I was thinking of having a 'WorldFactory' and/or a 'LevelFactory' aswell, and you could add Gameobjects to them, and when they destruct they free their Gameobjects aswell? Is that a proper way to manage lifetime?

Share this post


Link to post
Share on other sites
Hello, KaiserJohan.

As you probably know more or less, a smart pointer is a simulation of a regular pointer. It is just an abstract data type for strict memory management.

[color=#000000][font=sans-serif][size=3]The misuse of pointers is a major source of bugs: the constant allocation, deallocation and referencing that must be performed by a program written using pointers introduces the risk that a[/size][/font][/color] memory leak[color=#000000][font=sans-serif][size=3] will occur.[/size][/font][/color]

[color=#000000][font=sans-serif][size=3]Also, [/size][/font][/color][color=#000000][font=sans-serif][size=3]smart pointers may be implemented as a template class that mimics, by means of operator[/size][/font][/color] overloading[color=#000000][font=sans-serif][size=3], the behavior of traditional([/size][/font][/color]raw)pointers[color=#000000][font=sans-serif][size=3], (e.g.: dereferencing, assignment) while providing additional memory management algorithms.[/size][/font][/color]

[color=#000000][font=sans-serif][size=3]Smart pointers can facilitate "intentional programming"[/size][/font][/color][color=#000000][font=sans-serif][size=3] by expressing the use of a pointer in the type itself. For example, if a C++ function returns a pointer, there is no way to know whether the caller should delete the memory pointed to when the caller is finished with the information.[/size][/font][/color]



[color=#000000][font=sans-serif][size=3][font=monospace][CODE]some_type[color=#000040]*[/color] ambiguous_function[color=#008000]([/color][color=#008000])[/color][color=#008080];[/color] [color=#666666]// What should be done with the result?[/color][/CODE][/font][/size][/font][/color]


[color=#000000][font=sans-serif][size=3]Traditionally, this has been solved with comments, but this can be error-prone. By returning an [url="http://en.wikipedia.org/wiki/Auto_ptr"]auto_ptr[/url],[/size][/font][/color]



[color=#000000][font=sans-serif][size=3][font=monospace][CODE]auto_ptr[color=#000080]<[/color]some_type[color=#000080]>[/color] obvious_function1[color=#008000]([/color][color=#008000])[/color][color=#008080];[/color][/CODE][/font][/size][/font][/color]


[color=#000000][font=sans-serif][size=3]the function makes explicit that the caller will take ownership of the result, and furthermore, that if the caller does nothing, no memory will be leaked.[/size][/font][/color]

[color=#000000][font=sans-serif][size=3]I hope I was of help. 20+ years of programming experience.[/size][/font][/color]

[color=#000000][font=sans-serif][size=3]- Mikey[/size][/font][/color]

Share this post


Link to post
Share on other sites
[quote name='KaiserJohan' timestamp='1333133468' post='4926766']
Basically have a single look-up table to see if the item is not deleted everytime you use it, like overriding the '->' operator. That way the ObjectFactory could still track the Gameobjects.[/quote]

I see a tiny flaw in your concept. What is -> supposed to do if the object WAS deleted? Return 0, automatically have it dereferenced and crash? Or "secretly" redirect your call to a "null object" that simply does nothing?

I see only three scenarios.

a) You say "as long as anybody is still using that object, I must not delete it" -> use shared_ptr... you can still always remove it from whoever manages it, but you delay destruction until the user is done with it

b) You say "it is safe to assume by the time I clean up my table, nobody should be using that pointer anymore". In that case, use raw pointers or unique_ptr/auto_ptr (to clean up automatically when you remove the table)

c) You argue that "the user code should always make sure the pointer is still valid before using it". Use a single shared_ptr for the table and deal out weak_ptr to whoever uses it.

Or wrap it up in a way that returns a null object, if you really and absolutely don't want users to check the pointer. IF you can say "I don't care about nothing happening in that case. It still beats having to check the pointer every single time".

However, in a multi threaded situation (where objects might be used in more than one thread) I would always go with shared_ptr. Simply checking before use is worthless, when the object could be destroyed right between checking and using.

Share this post


Link to post
Share on other sites
On the boost smart_ptr;
I am using a custom allocator/deallocator (memorymgr). For some reason the compiler complaiins saying:

[code]Error 12 error C2661: 'boost::shared_ptr<T>::shared_ptr' : no overloaded function takes 2 arguments[/code]

But having read the documentation, I am quite sure thats supposed to be the syntax?
I am using boost 1.49.0


[code] template <class T>
inline static void DestroyObject(T* obj)
{
// not done yet
}


template <class T>
inline boost::shared_ptr<T> CreateObject()
{
boost::shared_ptr<T> ptr((T*) mMemoryMgr->Allocate(sizeof(T)),DestroyObject );


return ptr;
}[/code]

Share this post


Link to post
Share on other sites
Note that you should really pass constructed objects to the shared_ptr constructor. As it is you're passing uninitialized memory. In this case consider using placement new. Ex: [tt]new (mMemoryMgr->Allocate(sizeof(T))) T()[/tt].

Share this post


Link to post
Share on other sites
Hi, SiCrane.

I wouldn't recommend that tactic you're explaining. Constructed objects shouldn't have need to be passed to the shared_ptr constructor.

That's a big no-no right there.

20+ years of programming experience.

- Mikey

Share this post


Link to post
Share on other sites
What on earth are you talking about? The entire point of shared_ptr is to manage a fully constructed object. You give it a valid object and it calls cleanup when all the references to it are gone. Have you even ever looked at the shared_ptr documentation? This is the first line of code in the Best Practices section: [tt][color=#000000]shared_ptr<T> p(new Y);[/tt] Notice how the object passed to the shared_ptr is fully constructed.[/color]

Share this post


Link to post
Share on other sites
Hi again, SiCrane.

No, I have never researched the shared_ptr's documentation. I learned about pointers through my establishment of my first Assembly-generated game engine back many years ago. I started as a low-level programmer, and have been programming longer than you've probably been legal. I can give you at least 100 better solutions to your case.

However, given the many complex issues that may arise here, I think we should push this aside rather than debate.

- Mikey

Share this post


Link to post
Share on other sites
[quote name='MLillowitz' timestamp='1333364903' post='4927435']
No, I have never researched the shared_ptr's documentation.
[/quote]Have a look at it -- as SiCrane mentioned, the exact case you're recommending against is the very first example of correct and intended usage.

Perhaps you could explain [i]why[/i] you think it is a bad idea? Personally, I'd rather not "push this aside" given you've yet to explain [i]why[/i] you're recommending [i]against[/i] standard practice.

Share this post


Link to post
Share on other sites
Hello again, jbadams.

Let's drop this before it becomes a bigger debate, please.

You may check out my external blogs from my homepage regarding reasons, tutorials and articles on why I don't recommend what the other poster offered.

Thank you.

20+ years of programming experience.

- Mikey

Share this post


Link to post
Share on other sites
Perhaps you could at least link us to one example of an argument against the documented best practices?

It's kind of poor form to make a strong assertion in direct contradiction to the collective wisdom of thousands of programmers and then refuse to back up your opinion. Nobody's necessarily saying you're wrong, or that you shouldn't advocate what you're advocating. We just want to know your reasons.

At least do us the courtesy of expounding a bit. Who knows, maybe [i]we[/i] can learn something.


As it stands, your repeated "20+ years of experience" quips and refusal to support your statements leaves a rather poor taste in my mouth, personally.

Share this post


Link to post
Share on other sites
Hi, return0.

No, I'm not a "supertroll".

I'm sorry I've came across as dubious to you, but all my points will get across once my blog goes up soon.

Sorry for any inconvenience, and good luck to all your smarty pointers! :)

And it's not a manual post if it's copy/pasted. ;D

- Mikey

Share this post


Link to post
Share on other sites
Alright, let me sum this up. Someone is giving dubious advice, especially in this thread but also in a few others. That does not even count the thread full of weird they created themselves [1]. The suggested 'advice' goes against the documented best practice of a well-known, widely-used library (aka Boost aka What-would-have-been-in-the-standard-library-in-a-decent-modern-language). While claiming there are "100 better solutions" they are unwilling to name and/or describe even one, be it in this thread or a newly created one. Be it here or in other places the 'advice' is never explained, but all will be made clear once that person's site with their blog goes up. The site already has a dedicated name [2] but that name is (as of my whois queries just now) not even yet registered, making it perhaps unwise to throw the name around. In the absence of any verifiable facts the person also likes to claim "20+ years of programming experience" repeatedly all over the place. Did I miss anything?

At this point my troll radar is simply off the scale. Not even a good one because it's just too much of a caricature of a cliché arrogant but clueless 'developer'. Of course there is also the possibility it's simply a medicamentation issue. A younger me might have still believed I might against all odds learn something new and interesting from this but the current me has seen something very much like this too often to actually consider that.

[1] [url="http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__p__4926603#entry4926603"]http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__p__4926603#entry4926603[/url]
[2] [url="http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__view__findpost__p__4927772"]http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__view__findpost__p__4927772[/url]

Share this post


Link to post
Share on other sites
The requirement that shared_ptr has on the pointee is that for the one-argument ctor, the object pointed to shall be sourced from plain [b]new[/b], and as such should be destroyable with plain [b]delete[/b].

For the ctor that accepts a pointer and a deleter, the only requirement is that the expression [b]d(p)[/b] should be well-formed. This doesn't however mean that you can use the memory as an object unless you've explicitly constructed an object in it via placement new or some other method.

While you [i]can[/i] form a shared_ptr to uninitialized memory with a custom deleter, it's very counter-productive as you will not be able to use it as an object of that type and will most definitely invoke UB if you ever touch it.

Share this post


Link to post
Share on other sites
Hey, got a few more questions

1. I realised when I want to update all my Game Objects it seems there is an issue with weak_ptr.

For example, I have a vector of game objects that have registered that they want to receive Update() on each engine update (what is the best way to check that btw, besides a method call to the ObjectFactory that it wants to receive Updates()?)

Now on each engine update, when I access the GameObject in the weak_ptr I have to first go lock() and then get(). Lock creates and returns a shared_ptr, which is quite expensive is it not, to do on every update for all objects? Yet I don't want a shared_ptr in my ObjectFactory because then it would never go out of scope untill Engine destructs (as mentioned earlier in thread)

2. Multithreading; there will probably be trouble if one thread wants to DestroyObject() while an engine thread is Updating() that same object. Having to lock my vector every Update()/Create()/Destroy() seems really inefficient. What is the best way to solve this?
I suppose ObjectFactory could have an internal thread with a message queue, and that thread alone has access rights to explicitly create/destroy/access GameObjects... but isn't that alot of overhead? and dosn't the message queue itself need to be locked? Must also each Update() call from Engine be a message? Would be quite alot of messages.

Also how would it be possible? I mean I realistically can't block untill I get my object from the factory right?

Likewise I plan on using a Component-based model for my GameObjects, so I will have AudioManager, Renderer, etc likewise do their calculations (rather than each gameobject do itm from what i've read on data-driven design). Do they also need to have worker threads like that?

Share this post


Link to post
Share on other sites
Basically you have listed two good reasons why you should probably not store weak_ptr in your factory (which I'd call manager instead, since from a factory I'd expect nothing but creating objects). Based on your description this IS pretty much the owner of the objects, so if anything is storing a shared_ptr to them, it should be here. A somewhat weird way to still get what you want could be to check the number of references and remove the shared_ptr if this count is one (ie. this is the only one left). It probably beats a ton of lock() calls.

On the bright side, the object can not be destroyed in a different thread while you're running update, because you created a shared_ptr before calling it and hopefully don't let it go out of scope until update returns. You DO have to check the shared_ptr to be valid before calling update though (unless update itself is checking the pointer for null).

Share this post


Link to post
Share on other sites
According to boost documentation checking the ref count seems to be expensive though ([url="http://www.boost.org/doc/libs/1_49_0/libs/smart_ptr/shared_ptr.htm#use_count"]http://www.boost.org...r.htm#use_count[/url])

How does game engines typically handle game object creation? This cant be the first time someone bumps into this issue

Share this post


Link to post
Share on other sites
[quote name='MLillowitz' timestamp='1333411653' post='4927708']
20+ years of programming experience.
[/quote]
20+ years of sh*t coding still makes you a sh*t coder.

Share this post


Link to post
Share on other sites
[quote name='Cornstalks' timestamp='1333591955' post='4928364']
[quote name='MLillowitz' timestamp='1333411653' post='4927708']
20+ years of programming experience.
[/quote]
20+ years of sh*t coding still makes you a sh*t coder.
[/quote]

He is a troll, check his profile page, he has andy harglesis in his contacts (and andy has him) which says it all really. (its probably the same person even)

Share this post


Link to post
Share on other sites

This topic is 2078 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this