Jump to content

  • Log In with Google      Sign In   
  • Create Account


Destructors or Release methods?


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
17 replies to this topic

#1 arka80   Members   -  Reputation: 741

Like
0Likes
Like

Posted 17 February 2014 - 08:27 AM

Every time I design something I fall into this dilemma: should I clear things on destructors or should I make a (public) Release method? And if I make the Release method, should I automatically call it on destructor (again! ohmy.png ) or should I leave the programmer (well... me tongue.png ) free to call it whenever he wants?

 

Pros and cons that I can miss?


Edited by arka80, 17 February 2014 - 08:28 AM.


Sponsor:

#2 Buckeye   Crossbones+   -  Reputation: 4127

Like
1Likes
Like

Posted 17 February 2014 - 08:36 AM

Can you be a little more technical about what you mean by "clear things?" In general, each object should cleanup after itself. If it creates or allocates, it's responsible for releasing or deallocating its own resources. Some objects may need to create/release several times throughout its lifetime, as needed. Leaving those decisions to another object doesn't make sense.

 

Having a public Release method and also calling it in the destructor sounds like a recipe for disaster.


Edited by Buckeye, 17 February 2014 - 08:40 AM.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.


#3 arka80   Members   -  Reputation: 741

Like
0Likes
Like

Posted 17 February 2014 - 08:51 AM

I'm also of the idea that an object should clear itself in a transparent way, but in a lot of well known API objects has some kind of public Release method. Why?

 

EDIT: with "clear things" I mean the usual call of delete commands and pointers nullify


Edited by arka80, 17 February 2014 - 08:53 AM.


#4 KaiserJohan   Members   -  Reputation: 1099

Like
2Likes
Like

Posted 17 February 2014 - 09:09 AM

What language are you using?

 

If you are using C++, look into RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization).


Edited by KaiserJohan, 17 February 2014 - 09:10 AM.


#5 Aliii   Members   -  Reputation: 1438

Like
0Likes
Like

Posted 17 February 2014 - 09:14 AM

If it makes sense to release resources before the objects gets destructed (and its memory freed) then make a public method. If it doesnt then clean it up in the destructor and dont add an unnecessary protected: release() method.

 

pointer nullify? ....you mean after you delete something you set the pointer to nullptr so if someone calls delete again, it wont crash? Sounds bad. RAII would be more sane. And if you use raw pointers or arrays, make a has_memory_allocated or something member so you know that it can be freed(maybe the allocation has failed in the constructor).



#6 Buckeye   Crossbones+   -  Reputation: 4127

Like
1Likes
Like

Posted 17 February 2014 - 09:41 AM


a lot of well known API objects has some kind of public Release method. Why?

Because you don't delete those types of objects. You create them; you release them. Note: if you're talking about COM objects, you can also AddRef/Release in matched pairs.

 

To clarify, I'm assuming you're talking about an object that, when it's created or allocated, then creates and allocates for itself needed objects. E.g.,

Object::Object()
{
    Create( device ); // EDIT: or device->AddRef()
    GlobalAlloc( myMemory );
}

Object::~Object()
{
    if( device ) Release( device ); // Note: the object's responsibility.
    //device = NULL; // NO!!
    if( myMemory ) GlobalFree( myMemory ); // because this object allocated it.
    myMemory = NULL; // Not really necessary?
}

For COM objects, Release() must be public. How would you release it otherwise? smile.png

 

Releasing the device (in the above) may not result in the device's destruction, if other objects needed it, called device->AddRef, and haven't called Release yet. That's not this Object's responsibility. The device will take care of itself.


Edited by Buckeye, 17 February 2014 - 10:00 AM.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.


#7 dilyan_rusev   Members   -  Reputation: 886

Like
0Likes
Like

Posted 17 February 2014 - 10:09 AM

Well, if I understand correctly - you might want to "release" your memory from time to during the object's lifetime. If you were writing something like a smart pointer, then, sure, a release method that you call is a very sane thing to do, unless you want to duplicate code. Other than a smart pointer, I don't remember if I had to have a clean-up method besides the destructor.


Edited by dilyan_rusev, 17 February 2014 - 10:09 AM.


#8 Buckeye   Crossbones+   -  Reputation: 4127

Like
0Likes
Like

Posted 17 February 2014 - 10:42 AM


If you were writing something like a smart pointer, ...

I'm not as knowledgeable of smart pointers as I ought to be. However, do you mean shared pointer, perhaps? I was considering heap allocations needed only (exclusive ownership) by the object.


Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.


#9 King Mir   Members   -  Reputation: 1936

Like
2Likes
Like

Posted 17 February 2014 - 10:50 AM

You can have both, like std::fstream. There there exists a close method, which is called by the destructor if the file is not already closed. You can make it so that calling the clean-up method more than once is safe to do. More often, just releasing and cleaning up in the destructor is the best option. And because you do want to leave the system in a safe state, having clean-up as a method, but not as a destructor is usually bad. 



#10 DvDmanDT   Members   -  Reputation: 836

Like
1Likes
Like

Posted 17 February 2014 - 11:03 AM

The only times I've seen Release() methods are when there are matching AddRef() or similar, that is reference counted objects.

 

Edit: There are some more legitimate uses. For example a scripting language may require explicit release methods in order to function correctly/automatically. There are also some reasons to work with interfaces, such as when creating libraries, where it may be cleaner or more desirable to use explicit releasing. In such cases, it's not too uncommon to create RAII wrappers around those interfaces.


Edited by DvDmanDT, 17 February 2014 - 11:52 AM.


#11 phil_t   Crossbones+   -  Reputation: 3235

Like
0Likes
Like

Posted 17 February 2014 - 12:13 PM

I think the only time you should need a separate Release method (instead of just a destructor) is if:

- you are implementing your own reference counting (e.g. like a COM object does, paired with AddRef), or

- your object holds onto system resources and you want your object to be able release those important system resources before it goes out of scope or is destroyed (such as a File object releasing a file handle, a texture object releasing the underlying GPU resource, something like that).


Edited by phil_t, 17 February 2014 - 12:14 PM.


#12 ApochPiQ   Moderators   -  Reputation: 14673

Like
5Likes
Like

Posted 17 February 2014 - 01:05 PM

It sounds like there is some conflation of independent concepts.

 

 

A resource might be acquired and released; the interface for this is pretty much arbitrary and irrelevant.

 

 

A handle to a resource should obey RAII principles (assuming C++) and automatically acquire the resource during construction and release the resource during destruction.

 

 

An object might have handles to many resources, in which case it should still use RAII design. A handle should never be to more than one resource. A resource itself might be wrapped in a number of ways, but it should always be interacted with via a handle.

 

 

This solves all the problems that have been mentioned so far :-)



#13 cozzie   Members   -  Reputation: 1554

Like
0Likes
Like

Posted 17 February 2014 - 04:36 PM

Personally I use CComPtr for (d3d) objects and only set them to null in the destructor, no releasing/ release calls needed.

A stripped example:

#include <atlbase.h>

class CD3d
{
public:
	CComPtr<IDirect3DDevice9>	mD3ddev;		// smart pointer, first LPDIRECT3DDEVICE9

// etc

CD3d::CD3d()
{
	mD3ddev					= NULL;		// smart pointer
// etc

CD3d::~CD3d()
{
	// Smart pointers
	mD3ddev		= NULL;
// etc

This way I never have memory leaks.

Not 100% sure though if it's really necessary to set them to NULL / nullptr. If not, you could save yourself making your own constructor/destructor (rule of 3?) and let the compiler do it automatically for you.


Edited by cozzie, 17 February 2014 - 04:37 PM.


#14 frob   Moderators   -  Reputation: 19841

Like
6Likes
Like

Posted 17 February 2014 - 05:16 PM

Memory and resource management are super important.


Typically games follow a longer life pattern.

Construct lots of objects that stick around for a long time. Load and unload to a hierarchy of needs: Create it, configure it, load it, place it in the world, activate it. Then at a later time, deactivate it, remove it from the world, unload it, and eventually destroy it. Sometimes you will activate and deactivate an object many times. You may have some culling and resource management that mean objects exist for a long time but the resources are loaded and unloaded many times.

Constructor: Do the minimum amount of work to put the object in a known state. Initialize things, but usually set them to null. Do not allocate memory or other resources. For the love of all that is holy, DO NOT ACCESS THE DISK or other slow resources during construction. The goal is just enough to put something in a known state. Objects get constructed all the time. You make arrays that require construction, you make temporaries that require construction. Construction should be approximately instant.

Load, Acquire, or Connect functions. Use these as your objects do their job. Often games will try to bundle asset loading at a single time, such as level loads, so follow whatever pattern is appropriate for your game. This is where disk accesses, memory allocations, and other resource management should take place.

Unload, Release, and Disconnect functions. Use them. Rely on them. Call them when you are finished using the resource.

Destructor: As a fail-safe you can call whatever unload, release, or disconnect methods are available. Sometimes it is a good practice to add an assertion or other serious debug warning when the destructor unloads resources to ensure the condition is fixed.


Properly managing the lifetime of objects is one of a programmer's most important jobs. Do it poorly at your peril; resource leaks and corruption will quickly destroy the code base.
Check out my personal indie blog at bryanwagstaff.com.

#15 Aardvajk   Crossbones+   -  Reputation: 5857

Like
1Likes
Like

Posted 18 February 2014 - 02:25 AM

The problem as I see it is by having a Release() method, you are then implying that instances of your class can exist in two states - formed and released. After you call Release(), your object is still around and can still be operated upon, so your internals of the class and your users then need to code around this, checking the state of the object during any operations.

 

One of the strongest principles of encapsulation is class invariants - if I have a std::string object, I am guaranteed from the outside to have an object containing a valid string or the empty string. I don't have to check isValid() every time.

 

In an ideal world (in which I accept we are not) it is better to use object lifetime to model the lifetime of the resource being modelled, then your objects can and need only exist in a formed state.



#16 arka80   Members   -  Reputation: 741

Like
0Likes
Like

Posted 18 February 2014 - 02:48 AM

Thank you guys, I have a clear vision now, and think I'll get rid of Release methods for a while, since I use raw pointers and no ref counts. Long life to destructors.



#17 frob   Moderators   -  Reputation: 19841

Like
4Likes
Like

Posted 18 February 2014 - 03:00 AM


The problem as I see it is by having a Release() method, you are then implying that instances of your class can exist in two states - formed and released. After you call Release(), your object is still around and can still be operated upon, so your internals of the class and your users then need to code around this, checking the state of the object during any operations.
 
One of the strongest principles of encapsulation is class invariants - if I have a std::string object, I am guaranteed from the outside to have an object containing a valid string or the empty string. I don't have to check isValid() every time.

 

No, that isn't what I am saying at all.

 

You want something that can be created almost instantly.  Think of it as an empty string, or an empty vector, or an empty hash table. That doesn't mean it is ill-formed, that doesn't mean it is invalid, that doesn't mean RAII doesn't apply. It is well-formed and complete. It just means it is currently empty.

 

Consider the ever-popular epic work, the GameObject class.  It serves as the base class of everything scriptable and a game world can have tens of thousands or even hundreds of thousands of them. A continuous world might have many million game objects in it. 

 

Usually this thing grows to be MASSIVE. It isn't just a single transformation with position and orientation. It is usually hooked up to lots of components. it can be associated with models and textures and animations and shaders. It can be visible to the player, or not. It can be active and alive and important to the player, or it can be on the other side of the game universe just sitting around waiting for the player to walk across the map. Each child class can add their own details and containers and resource needs. It gets connected and disconnected to systems everywhere in the game.

 

When you start up the game you generally don't want every game object to load every detail immediately when it is constructed. For a large game the process could take five, ten or twenty minutes. For a continuous world game, you could easily exhaust all your memory.

 

When I say "Give me an array of 50 game objects", the absolute last thing I want to have happen is to cascade into 50 meshes loading, 50-300 textures loading, 500+ animations loading, 30+ shaders loading, tons of audio loading, followed by objects self-registering inside the world and the spatial trees, appending themselves to the render lists, including itself in the pathfinder, and so on. Such a process might mean thousands of calls to the disk as file names are turned into data streams and each stream individually pulled from a slow-spinning platter, followed by hundreds of calls into some of the most expensive systems of the game. No! I want 50 completely empty game objects that I can use for my own purposes immediately.

 

The key detail is that you don't do all of those things during construction and destruction. Instead, you do it at a time that you can control, a time that is appropriate. You can replace the very expensive models and textures and animations with simple proxies when they are off screen; they can be reloaded in moments when the player approaches them. You can remove proximity checks when the player is on the other side of the world, the land mine won't be stepped on when the player is on the other side of the world. 

 

 

I am not saying to not initialize your objects. Far from it. Everything should be in a valid state. What I am saying is that "valid state" does not mean "requires 47MB of resources", but can also mean "empty", "ready for delayed loading", "proxied", and potentially much more. Careful resource management is very important in games. 


Edited by frob, 18 February 2014 - 03:29 AM.

Check out my personal indie blog at bryanwagstaff.com.

#18 Aardvajk   Crossbones+   -  Reputation: 5857

Like
1Likes
Like

Posted 18 February 2014 - 03:52 AM

frob - Yes, I understand that you weren't suggesting uninitialized objects. What I am saying is that you can model what you are talking about using composition and object lifetime, then you don't need to worry about the "empty" state if the empty state only needs to exist in order to accommodate delayed loading for example.

 

If the empty state is not a conceptual feature of the object, but an implementation detail to allow for delayed loading, this can be expressed via having the object contained in a proxy then the acquisition (delayed) and freeing of the actual object can be modelled using the object lifetime, just as a member of a proxy object.

 

The proxy can then handle releasing its owned resource, possibly in a generic way and the actual resource itself need only maintain as invariant its populated state. Then when you have acquired the resource from the proxy, you always have a guaranteed formed object to work with and the proxy takes care of ensuring it is populated, perhaps in a just-in-time manner.

 

But this is design nitpicking and probably not very related to the real world. In reality of course a Mesh object will have a vertexCount == 0 state and if this mechanism is used to implement a decision at each use as to whether to load it or not before use, its much the same thing I suppose, except this is unwrapped so has to be performed all over the place.


Edited by Aardvajk, 18 February 2014 - 03:54 AM.





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