The C++ language leaves memory management in the hand of the programmer, that is the reason for which you can find that level of confusion.
Repeating what Luchian Grigore said there are three main types of memory
- automatic storage (stack)
- dynamic storage (heap)
- static storage
If you allocate an object in automatic storage the the object will be destroyed once the scope is terminated; for example
void foo() {
MyClass myclass_instance;
myclass_instance.doSomething();
}
in the above case when the function terminates myclass_instance
is destroyed automatically.
If you instead allocate an object in the heap with new
then it's your responsibility to call the destructor with delete
.
In C++ also an object can have sub-objects. For example:
class MyBiggerClass {
MyClass x1;
MyClass x2;
...
};
those sub-objects are allocated in the same memory the containing object is allocated to
void foo() {
MyBiggerClass big_instance;
MyBiggerClass *p = new MyBiggerClass();
...
delete p;
}
in the above case the two sub-objects big_instance.x1
and big_instance.x2
will be allocated in automatic storage (stack), while p->x1
and p->x2
are allocated on the heap.
Note however that you don't need in this case to call delete p->x1;
(compile error, p->x1
is not a pointer) or delete &(p->x1);
(syntactically valid, but logical mistake because that it wasn't allocated explicitly on the heap, but as a sub-object of another object). Deleting the main object p
is all that is needed.
Another complication is that an object may keep pointers to other objects instead of including them directly:
class MyOtherBigClass {
MyClass *px1;
MyClass *px2;
};
in this case it will be the constructor of MyOtherBigClass
that will have to find the memory for the sub-objects and it will be ~MyOtherBigClass
that will have to take care of destroying the sub-objects and freeing the memory.
In C++ destroying a raw pointer doesn't automatically destroy the content.
Base classes in simple cases can be seen just as hidden embedded sub-objects. I.e. it's like if an instance of the base object is embedded in the derived object.
class MyBaseClass {
...
};
class MyDerivedClass : MyBaseClass {
MyBaseClass __base__; // <== just for explanation of how it works: the base
// sub-object is already present, you don't
// need to declare it and it's a sub-object that
// has no name. In the C++ standard you can find
// this hidden sub-object referenced quite often.
...
};
This means that the destructor of the derived object doesn't need to call the destructor of the base object because this is taken care by the language automatically.
The case of virtual bases is more complex, but still the invocation of base destructors is automatic.
Given that memory management is in the control of the programmer there are a few strategies that have emerged to help programmers avoiding making a mess of intricate code that always ends up in object leaks or multiple destruction.
Plan carefully how you are going to handle lifetime of the instances. You cannot just leave this as an afterthought because it will be impossible to fix later. For every object instance it should be clear who creates and who destroys it.
When it's impossible to plan ahead of time when an object should be destroyed then use reference counters: for every object keep track how many pointers are referencing it and destroy the object once this number reaches zero. There are smart pointers that can take care of this for you.
Never keep around a pointer to an object that has already been destroyed.
Use containers that are classes designed explicitly to handle the lifetime of contained objects. Examples are std::vector
or std::map
.