Smart pointers question

Started by
35 comments, last by Hodgman 12 years ago
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



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;
}
}
//......




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
Advertisement
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.

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?


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 can't 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).
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?
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]

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[/font] memory leak[color=#000000][font=sans-serif]

will occur.[/font]

[color=#000000][font=sans-serif]

Also, [/font][color=#000000][font=sans-serif]

smart pointers may be implemented as a template class that mimics, by means of operator[/font] overloading[color=#000000][font=sans-serif]

, the behavior of traditional([/font]raw)pointers[color=#000000][font=sans-serif]

, (e.g.: dereferencing, assignment) while providing additional memory management algorithms.[/font]

[color=#000000][font=sans-serif]

Smart pointers can facilitate "intentional programming"[/font][color=#000000][font=sans-serif]

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.[/font]



[color=#000000][font=sans-serif]

[font=monospace]some_type[color=#000040]* ambiguous_function[color=#008000]([color=#008000])[color=#008080]; [color=#666666]// What should be done with the result?[/font][/font]


[color=#000000][font=sans-serif]

Traditionally, this has been solved with comments, but this can be error-prone. By returning an auto_ptr,[/font]



[color=#000000][font=sans-serif]

[font=monospace]auto_ptr[color=#000080]<some_type[color=#000080]> obvious_function1[color=#008000]([color=#008000])[color=#008080];[/font][/font]


[color=#000000][font=sans-serif]

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.[/font]

[color=#000000][font=sans-serif]

I hope I was of help. 20+ years of programming experience.[/font]

[color=#000000][font=sans-serif]

- Mikey[/font]


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 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.
f@dzhttp://festini.device-zero.de
On the boost smart_ptr;
I am using a custom allocator/deallocator (memorymgr). For some reason the compiler complaiins saying:

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

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


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;
}
The function DestroyObject is a template that has to be instantiated.
[source]
boost::shared_ptr<T> ptr(... ,DestroyObject<T> );
[/source]
Genius, thanks!
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: new (mMemoryMgr->Allocate(sizeof(T))) T().

This topic is closed to new replies.

Advertisement