Object Life Cycle
"An object is a region of storage that has a type and a value" (Lospinoso). Whenever you declare a variable, you create an object.
Storage duration and lifetime
An object's storage duration begins when storage is allocated for that object, and ends when storage is deallocated for that object. (Memory is reserved for the object during this period.)
An object's lifetime begins when the object's constructor returns, and ends just before the object's destructor is called. (You can use the object during this period.)
Generally:
- Object's storage duration begins — storage is allocated
- Object's lifetime begins — object's constructor returns
- (... you can use the object in your program ...)
- Object's lifetime ends — object's destructor is called
- Object's storage duration ends — storage is deallocated
Types of storage duration
Instead of relying on the garbage collector (aka. automatic memory manager) to automatically deallocates the memory of objects you no longer use, you should know these types of memory management in C++.
Automatic storage duration
All local variables and function parameters have this storage duration.
- Storage is allocated at the start of the enclosing code block (
{) - Storage is deallocated at the end of the enclosing code block (
})
// `a` and `b` have automatic storage duration
void addOne(int a) {
int b = 1;
return a + b;
}
Static storage duration
Variables declared at global scope (or namespace scope), including those declared with the static or extern keyword, have this storage duration.
- Storage is allocated when the program begins
- Storage is deallocated when the program ends
// `a` and `b` have static storage duration
static int a = 100;
extern int b = 200;
// `c` has static storage duration
struct MyClass {
static int c = 300;
}
// `d` has static storage duration
int main() {
static int d = 10;
}
Local static variables
Localstatic variables (cppreference.com):- the storage is allocated when the program begins
- the value is initialized the first time control passes through their declaration (except for zero or constant initialization, which can be done before the block is first entered).
Normal class member vs static class member
- Local class members follow the class' storage duration
- Static class members have static storage duration
Thread-local storage duration
Variables declared with thread_local have thread-local storage duration.
- Storage is allocated when the thread begins
- Storage is deallocated when the thread ends
// `tl_a` has thread-local storage duration
thread_local int tl_a = 1;
// `tl_b` has thread-local storage duration
void myFunction() {
static thread_local int tl_b = 5;
}
thread_local implies static
thread_local can be combined with the static or extern keyword. If none is specified, static is implied.
For the initialization of local thread_local variables, see "local static variable" side note above.
Dynamic storage duration
Objects with dynamic storage duration are allocated and deallocated upon your request. These objects are also called dynamic objects.
- To allocate storage, use the
newexpression - To deallocate storage, use the
deleteexpression
int main() {
// Simple example with variables
int* my_int_ptr = new int{ 42 };
printf("%d\n", *my_int_ptr); //=> 42
delete my_int_ptr;
// With array, the length does not need to be a constant.
// (The length could also be inferred from the init list.)
const int length = 5;
int* my_arr_ptr_1 = new int[length]{};
int* my_arr_ptr_2 = new int[]{ 111, 222 };
printf("%d %d\n", my_arr_ptr_1[0], my_arr_ptr_1[1]); //=> 0 0
printf("%d %d\n", my_arr_ptr_2[0], my_arr_ptr_2[1]); //=> 111 222
// `delete[]` tells the CPU that it needs to clean up multiple variables
// (`new type[]` keeps track of how much memory was used)
delete[] my_arr_ptr_1;
delete[] my_arr_ptr_2;
}
You have to make sure that you delete dynamic objects once you no longer use them (e.g. at the end of the scope or inside the class' destructor).
Not doing this will cause memory leak, the condition where your program's memory usage keeps increasing because it does not release the memory it no longer uses.
Dynamic array vs fixed array
Dynamic arrays (allocated via new type[]) are stored in the heap, as opposed to fixed arrays which are stored in the stack.
This allows you to allocate large-sized arrays (e.g. arrays with 1,000,000 elements).
Storage duration example
The following code:
struct Tracer {
const char* const name;
Tracer(const char* name) : name{name} {
printf("Constructed: %s\n", name);
}
~Tracer() {
printf("Destructed: %s\n", name);
}
};
static Tracer t1{ "Global static variable" };
thread_local Tracer t2{ "Thread-local variable" };
int main() {
// Start of program:
//=> Constructed: Global static variable
const Tracer* t2_ptr = &t2;
//=> Constructed: Thread-local variable
Tracer t3 = { "Automatic variable" };
//=> Constructed: Automatic variable
const auto* t4 = new Tracer{ "Dynamic variable" };
//=> Constructed: Dynamic variable
// End of function:
// Note that 'Dynamic variable' is not destructed!
//=> Destructed: Automatic variable
//=> Destructed: Thread-local variable
// End of program:
//=> Destructed: Global static variable
}
will produce the following output:
Constructed: Global static variable
Constructed: Thread-local variable
Constructed: Automatic variable
Constructed: Dynamic variable
Destructed: Automatic variable
Destructed: Thread-local variable
Destructed: Global static variable
References
- C++ Crash Course (Josh Lospinoso) — 4. Object Life Cycle
- storage duration vs lifetime — https://stackoverflow.com/questions/21392160/storage-duration-vs-lifetime
- 6.11 — Scope, duration, and linkage summary — https://www.learncpp.com/cpp-tutorial/scope-duration-and-linkage-summary/
- 11.12 — Dynamically allocating arrays — https://www.learncpp.com/cpp-tutorial/dynamically-allocating-arrays/
- Storage class specifiers — https://en.cppreference.com/w/cpp/language/storage_duration
- Static array vs. dynamic array in C++ — https://stackoverflow.com/questions/2672085/static-array-vs-dynamic-array-in-c