Jump to content

  • Log In with Google      Sign In   
  • Create Account

Smart pointers question


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
36 replies to this topic

#1 KaiserJohan   Members   -  Reputation: 1156

Like
0Likes
Like

Posted 29 March 2012 - 03:49 PM

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

Sponsor:

#2 SiCrane   Moderators   -  Reputation: 9596

Like
2Likes
Like

Posted 29 March 2012 - 03:59 PM

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.

#3 turch   Members   -  Reputation: 590

Like
0Likes
Like

Posted 30 March 2012 - 07:22 AM

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

#4 KaiserJohan   Members   -  Reputation: 1156

Like
0Likes
Like

Posted 30 March 2012 - 12:51 PM

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?

#5 MLillowitz   Banned   -  Reputation: 23

Like
-8Likes
Like

Posted 30 March 2012 - 06:32 PM

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.

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 memory leak will occur.

Also, smart pointers may be implemented as a template class that mimics, by means of operator overloading, the behavior of traditional(raw)pointers, (e.g.: dereferencing, assignment) while providing additional memory management algorithms.

Smart pointers can facilitate "intentional programming" 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.



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]



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



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



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.

I hope I was of help. 20+ years of programming experience.

- Mikey

#6 Trienco   Crossbones+   -  Reputation: 2172

Like
1Likes
Like

Posted 30 March 2012 - 11:32 PM

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

#7 KaiserJohan   Members   -  Reputation: 1156

Like
0Likes
Like

Posted 31 March 2012 - 02:33 PM

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


#8 Brother Bob   Moderators   -  Reputation: 8195

Like
2Likes
Like

Posted 31 March 2012 - 02:54 PM

The function DestroyObject is a template that has to be instantiated.
boost::shared_ptr<T> ptr(... ,DestroyObject<T> );


#9 KaiserJohan   Members   -  Reputation: 1156

Like
0Likes
Like

Posted 31 March 2012 - 03:03 PM

Genius, thanks!

#10 SiCrane   Moderators   -  Reputation: 9596

Like
1Likes
Like

Posted 31 March 2012 - 03:03 PM

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().

#11 MLillowitz   Banned   -  Reputation: 23

Like
-14Likes
Like

Posted 01 April 2012 - 03:36 AM

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

#12 SiCrane   Moderators   -  Reputation: 9596

Like
0Likes
Like

Posted 01 April 2012 - 06:09 AM

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: shared_ptr<T> p(new Y); Notice how the object passed to the shared_ptr is fully constructed.

#13 MLillowitz   Banned   -  Reputation: 23

Like
-14Likes
Like

Posted 02 April 2012 - 05:08 AM

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

#14 jbadams   Senior Staff   -  Reputation: 18577

Like
0Likes
Like

Posted 02 April 2012 - 04:54 PM

No, I have never researched the shared_ptr's documentation.

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 why you think it is a bad idea? Personally, I'd rather not "push this aside" given you've yet to explain why you're recommending against standard practice.

#15 MLillowitz   Banned   -  Reputation: 23

Like
-11Likes
Like

Posted 02 April 2012 - 06:07 PM

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

#16 ApochPiQ   Moderators   -  Reputation: 15699

Like
4Likes
Like

Posted 02 April 2012 - 06:19 PM

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

#17 return0   Members   -  Reputation: 444

Like
3Likes
Like

Posted 02 April 2012 - 06:24 PM

Are you really typing "20+ years of experience" as a manual post signature, coupled with giving very dubious advice?

Supertroll?

#18 MLillowitz   Banned   -  Reputation: 23

Like
-3Likes
Like

Posted 02 April 2012 - 11:12 PM

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

#19 BitMaster   Crossbones+   -  Reputation: 4083

Like
0Likes
Like

Posted 03 April 2012 - 01:21 AM

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] http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__p__4926603#entry4926603
[2] http://www.gamedev.net/topic/622666-can-somebody-here-help-me-with-directx/page__view__findpost__p__4927772

#20 Zao   Members   -  Reputation: 880

Like
0Likes
Like

Posted 03 April 2012 - 04:07 AM

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 new, and as such should be destroyable with plain delete.

For the ctor that accepts a pointer and a deleter, the only requirement is that the expression d(p) 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 can 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.
To make it is hell. To fail is divine.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS