Object Lifecycle
Inheritance vs. Containers¶
It may happen that we want to store elements of a polymorphic type in some container. For example, we may want to store different types of Instrument objects such as Piano, Flute, Drum, etc. Every instrument can produce sound, but for an arbitrary Instrument object it is hard to tell how it will sound.
In such cases, the play() method of the Instrument class should be virtual, so each instrument can return its own specific sound.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Output
???
The issue is visible: copying the Flute object by value into the vector behaves like pass-by-value. The object loses its derived properties, so the polymorphic call cannot take effect.
We could store references or pointers instead, but references cannot be placed into containers.
Object Management¶
In Java we have already seen that the program uses different memory regions. Until now, every object we created was created locally, on the stack. Such objects live only until the execution leaves the block in which they were created, at which point their destructors run and memory is freed.
To keep an object alive for the entire program, we could make it global, but this is usually bad practice.
In C++, we can create objects that outlive their creating block without being global, using dynamic allocation. However, unlike Java, C++ does not have a garbage collector — freeing memory is the programmer’s responsibility.
Dynamic Memory Management¶
Dynamic memory is allocated on the heap, which is larger than the stack but must be explicitly requested, is slower, and is not automatically managed. We use pointers to access dynamically allocated memory.
In C++ we use the new operator. On failure it throws std::bad_alloc. Example:
1 2 3 4 5 6 7 8 | |
We may also use new (nothrow) to avoid exceptions.
Arrays are allocated with new[], and freed with delete[].
1 2 | |
Summary¶
| Allocated cells | Operator | Error handling | Release |
|---|---|---|---|
| 1 | new | try-catch | delete |
| many | new[] | try-catch | delete[] |
| 1 | new(nothrow) | check pointer | delete |
| many | new(nothrow) | check pointer | delete[] |
Dynamic Data Members in Classes¶
Classes often allocate memory dynamically for their internal data. Such memory must be released in the destructor.
1 2 3 4 5 6 7 8 9 10 11 12 | |
Every new must correspond to a delete (or delete[]).
Inheritance vs. Containers Revisited¶
We can store employees and researchers in containers. Copying objects by value destroys polymorphism — only the base portion is copied.
Using pointers preserves polymorphism:
1 2 3 4 5 6 7 8 | |
Virtual Destructor¶
If we delete derived objects through a base-class pointer, the base destructor must be virtual, otherwise only the base destructor runs, and the derived part leaks memory.
Correct implementation:
1 2 3 4 5 | |
With a virtual destructor, deleting via a base pointer correctly invokes the derived destructor as well.
Created: 2025-11-27