Cleanup After C++

Started by
19 comments, last by ConayR 16 years, 4 months ago
Quote:Original post by NightCabbage
Ok, I guess I just need to get my head around cleaning stuff up :)

So if I make anything, a string, an array, a texture, a struct, etc. I have to clean it up, right?

(by setting it to NULL? or calling the destructor? or just when it loses scope?)

To simplify things, I'm gonna pretend that all memory is either allocated on the stack (local) or the heap (dynamic). If you don't know about the stack and the heap, get yourself an "Intro to operating systems" book.

In Java, when you create a new object, the object itself is created on the heap, and your local variable (which references that new object) exists on the stack. Stack variables are local, so they can go out of scope. When your local reference goes out of scope, there are no more references to the object, so Java will automagically delete the object from the heap for you.

In C++, things are a bit trickier - you can choose to put objects on either the stack or the heap! Also, the language doesn't keep track of heap objects for you, which means if you don't have a pointer (C++ pointer = Java reference) to the object anymore, it will be leaked.

If you create an object on the stack, it will clean itself up when it goes out of scope. If you create an object on the heap, it will remain there until you delete it.

class Object{   Object() { /*constructor code*/ }  ~Object() { /*destructor  code*/ }  void DoSomething() {}};void CreateOnStack(){  if( true )  {    Object bob;//this creates a new object on the stack and calls the constructor    bob.DoSomething();//When using an object directly, use the "." operator  }//the object goes out of scope here and automatically calls the destructor}void CreateOnHeap(){  //N.B. This is a 'pointer' to an object (because of the *)  Object* bob;//this creates a local variable which *can* contain a memory address of an object  bob = new Object;//this creates a new object on the heap, calls the constructor and stores it's address in 'bob'  bob->DoSomething();//When using an object through a pointer, use the "->" operator  delete bob;//this calls the destructor, and releases the memory from the heap  bob = NULL;//bob still contains the memory address that the object was using, but the object is now deleted! So it's a good idea to throw away that old address by setting it to NULL!}void CreateLeak(){  if( true )  {    Object* bob = new Object;//Create object on heap, call constructor  }  //Oh no! Our pointer has gone out of scope, but the object still exists on the heap. We no longer know what it's address is, so we can't possibly delete it!}void CreateDanglingPointer(){  Object* bill = NULL;//If a pointer hasn't been set to an object yet, it's a good idea to set it to NULL/zero, so that you know it's not pointer to anything!  if( true )  {    Object* bob = new Object;//Create object on heap, call constructor    bill = bob;//copy the address into bill as well    delete bob;//Call destructor, free memory    bob = NULL;//Good practice  }  bill->DoSomething();//This will crash! As bill contains the memory address of an object which has been deleted!}
Advertisement
Quote:Original post by Hodgman
If a program uses the RAII idiom (which many will argue is a very good practice in C++ programming), then respecting the destructor is imperative!


NightCabbage: you must Must MUST(!) pay attention to what Hodgman said, here! In C++ scope does the equivalent of garbage collection in Java.

Cleanup in C++ should be as automatic as it is in Java or any other garbage collected language, but it is different.

If you allocate memory in a constructor, you deallocate in it a destructor. You also make sure copying is defined correctly[1]. This is one of the differences between C++ and Java, say -- that of preferring value semantics over reference semantics.

You will find, however, that a lot of the work has been done for you already. By using the standard library containers such as std::vector instead of arrays, std::list instead of a hand-crafted linked-list and so on, you shouldn't even need to think about allocation, deallocation or clean-up of any kind. Similarly, if you ever 'new' anything[2], put it in to a smart pointer or some kind of resource-managing object straight away. Cleanup is then done implicitly by the smart pointers destructor.

If you get in to the habit of using RAII generously, you will find that writing exception safe code is actually easier than it is in Java or C# ('using'/IDisposable was invented to allow an approximation of RAII in C#). If you miss a 'finally' clause in a Java program for example, you could have a long standing resource leak (such as a database connection or OpenGL texture) that will only get reclaimed on the next collection which is at who-knows-when.[3]

[1] Look up the rule of three
[2] There is the odd case where keeping the raw pointer is a good idea. But they are extremely rare, at least in my experience.
[3] I'm not slamming C# or Java here. I use them, too. But RAII is a powerful tool, not an inconvenience.
Thanks guys! You're helping me a lot! :D

One quick question...

I'm having a problem where I need to declare a global variable (a pointer to an object) and then initialize it in the body of my code...

Here's what I'd do in java:

Animal dog;public main(){    int legs = 4;    dog = new Animal(legs);}


Now this makes sense to me.

And I know I can do it the same way in C++.
(the only difference being I'd declare Animal* dog; instead)

But I also wonder if I should be able to do it another way (without using new)?

Or maybe not, now that I think about it.

So a local declaration (in scope) puts the object on the stack, and the new keyword puts it in the heap - is that right?

So in this case, I have to put it on the heap, because there's no scope for it.
And I just have to manage the variable myself (eg. here's where a leak could possibly occur)

Am I getting it now? :)

[Edited by - NightCabbage on November 26, 2007 8:42:36 PM]
Quote:So in this case, I have to put it on the heap, because there's no scope for it.


Hmm, semantics and details.

The pointer itself is allocated on stack, or stored in registers. It persists as long as the variable remains in scope. After it's gone, the destructor gets called (pointless for basic types) and the memory is "de-allocated" (stack is popped).

The memory you allocated with new however remains in use until application remains alive.

A more practical example would be this:
public main(){    int legs = 4;    Animal dog(legs);} // dog's destructor is called// int destructor is called (not really)


When allocating on stack, you do not need to worry about destruction. Basic types have destructor semantics fully supported (for templates), although they don't do anything and won't actually be called (you can call them though)



Your example however is wrong. What you do is allocate an Animal named dog in global scope, then try to assign it a pointer. This will fail unless animal overloads such assignment operator.

Animal *dog; // pointer to dog, dog doesn't exist yetpublic main(){    int legs = 4;    dog = new Animal(legs); // a new instance of dog is created}// you have leaked memory, namely the Animal you allocated


Simple rule - if you call new, you must call delete. Once for each.

Quote:Here's what I'd do in java:


typedef boost::scoped_ptr<Animal> AnimalPtr;public main(){    int legs = 4;    AnimalPtr dog( new Animal(legs));}// last reference to dog is gone, deallocate memory


Using smart pointers, you can use same methodology as in memory managed languages.
Quote:Original post by NightCabbage
But I also wonder if I should be able to do it another way (without using new)?


If you really need dog to have global scope:

Animal *dog;int main(){    Animal Dog(/*whatever*/);    dog = &Dog;//OR   std::auto_ptr<Animal> Dog( new Animal(/*whatever*/) );   dog = Dog.get();}


In either case, dog will be cleaned up when Dog goes out of scope (i.e., when main exits).
Antheus - "your example is wrong"

Did you mean this example?

Animal dog;public main(){    int legs = 4;    dog = new Animal(legs);} // dog still exists


(that was in Java)

in C++ it would be:

Animal* dog;public main(){    int legs = 4;    dog = new Animal(legs);} // dog still exists


Is that what you meant?

Driv3MeFar - thanks, that was what I was wondering :)
Quote:Original post by NightCabbage
Surely when the game exits ALL memory would be cleared?

ie. that is handled by Windows


Only if you are ignoring Win9x/ME since those systems won't clean up anything for you.

There really is no point in hoping that the user runs an OS that cleans up your mess when you're perfectly capable of doing it yourself.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
Quote:Original post by SimonForsman
Quote:Original post by NightCabbage
Surely when the game exits ALL memory would be cleared?

ie. that is handled by Windows

Only if you are ignoring Win9x/ME since those systems won't clean up anything for you.

I'm finding this very difficult to swallow. I can't seem to find proof one way or another, but Windows 95 ran in 32-bit protected mode. It's almost difficult not to release memory for a 'sandboxed' program when it terminates. Not to mention the volumes of empirical evidence from my days developing substandard software on Windows 98 [rolleyes].

I know that system-memory-leaks were common in DOS, and I could believe the same for Windows 3.1 as it recycled a lot of DOS code (even though it ran in protected mode). So... citation needed?
Ring3 Circus - Diary of a programmer, journal of a hacker.
9x did clean up for you, however it's drivers were notorious for leaking if not properly terminated. So you wouldn't leak from simple allocations, but if you had a shoddy graphics or sound driver you could leak resources from them. 2k/XP seem a lot better in terms of having a stable driver architecture so you get less of that.

Win 3.1 was a whole different ballgame, due to the way it worked there was minimal memory protection between apps and minimal cleanup on exit. It was very easy to leak most of your memory away from a single misbehaving app.
Quote:Original post by Jerax
9x did clean up for you, however it's drivers were notorious for leaking if not properly terminated. So you wouldn't leak from simple allocations, but if you had a shoddy graphics or sound driver you could leak resources from them. 2k/XP seem a lot better in terms of having a stable driver architecture so you get less of that.

Win 3.1 was a whole different ballgame, due to the way it worked there was minimal memory protection between apps and minimal cleanup on exit. It was very easy to leak most of your memory away from a single misbehaving app.


you're probably right, was a long time since i used those systems.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!

This topic is closed to new replies.

Advertisement