Constructors and Destructors
The destructor is therefore not called when the name is mentioned, and it is not called in the middle of the block; it is called at the end of the scope.
Why Objects Need a Construction Step
Every object in a program exists for some span of time. That span has a beginning and an end, and most of the confusion beginners face with objects comes from guessing those two points incorrectly. Instead of memorizing a list of rules, this article follows one object at a time and asks the same two questions each time: when does the object come into existence, and what event brings it to an end?
Lifetime does not start the moment you mention an object's name in a line of code. An object has to be constructed, meaning initialized, before it is used. Construction is the step that prepares the object so that the rest of the program can rely on it. Once construction has happened, the object is in a usable state, and it stays that way until the point where it is destroyed.
An object must be constructed before it is used, and it is destroyed at the end of its scope. Scope is the region of code bounded by braces, and for the most common case the closing brace is the event that ends the object's life.
Tracing a Scope-Based Object
Scope-based lifetime is the most common case. The lifetime of an object is determined by the scope in which it was declared, and the object is destroyed at the end of that scope. The scope is the region of code bounded by braces, so the object's life is tied to the closing brace.
Read the timeline from left to right. The object is declared, which is the point where its name appears. Construction then initializes it, and only after that can the rest of the block rely on it. The object remains in use until the closing brace is reached. At that point the destructor runs and the object's life ends. The destructor is therefore not called when the name is mentioned, and it is not called in the middle of the block; it is called at the end of the scope.
before block
construct
inside block
destroy
after blockProbe()is the constructor; it runs when p is declared- ~
Probe()is the destructor; it runs at the closing brace of the inner block - The inner braces define the scope that controls p's lifetime
What do you think happens?
Before you look at the output, predict the order in which the five lines print. Where does the destroy line appear?
Reveal answer
before block, construct, inside block, destroy, after block. The destroy line appears after inside block because the destructor runs at the closing brace of the inner block, before the program continues to after block.
The construct line appears at the declaration of p, which is the point of construction. The destructor is tied to the end of the scope that contains p, so it runs before the statement that follows that block.
Objects Created with new
Scope-based lifetime does not cover every case. An object created with new is not tied to the block where the new expression appears. According to the rule for this case, such an object lives until it is destroyed by delete. The programmer, not the closing brace, decides when the object's life ends.
Follow the states in order. Creating the object with new allocates it and constructs it, so it becomes usable. Unlike the scope-based case, reaching the end of the block does nothing to it. The object stays alive and can be passed around the program. Only when delete is called does destruction happen. The source illustrates this with a function that creates an object and returns: the object is still alive afterward, and the destruction message appears only at the delete line.
Tracing a dynamic object across a function return
A function creates an object with new, prints a message, and returns. The caller later calls delete on the pointer. When does the object's destructor run?
- 1Construction
The new expression allocates the object and runs its constructor. The object is usable from this point.
- 2Function returns
The function's scope ends, but the object was not declared as a local variable, so the closing brace does not destroy it.
- 3Object still alive
The caller holds the pointer, and the object continues to exist, because its lifetime is governed by delete.
- 4delete call
The programmer calls delete. This is the event that names the end of the object's life.
- 5Destruction
The destructor runs at the delete line, and the storage is released.
The destructor runs at the delete line, not when the function returns.
Scope-Based Versus Dynamic Lifetime
Both kinds of object follow the same first step, which is construction before use. They differ only in the event that ends their life. The table places the two approaches side by side so you can see which event controls each one.
| Question | Scope-based lifetime | Dynamic lifetime (new and delete) |
|---|---|---|
| How is the object created? | By a declaration inside a block | By a new expression |
| What ends its life? | The closing brace of the enclosing block | A matching delete call |
| Who decides the end point? | The scope structure of the code | The programmer, by calling delete |
| Does leaving the block destroy it? | Yes | No |
| Does construction still come first? | Yes | Yes |
Both lifetimes begin with construction; the event that ends them is what differs.
Two further cases complete the picture. A namespace object has its point of destruction at the end of the program. A member object has its point of destruction determined by the point of destruction of the object of which it is a member. A member does not choose its own end; it inherits the end point of the object that contains it.
Given a local variable declared inside a function body, a heap object created with new and stored in a pointer, and a member variable inside a class object that is itself a local variable, state for each one which event ends its life.
Hints
- Ask whether the object was declared by a plain declaration or created with new.
- For a member, look at the object that contains it and follow that object's end point.
The Vector Example: Acquiring and Releasing
A container is an object that holds a collection of elements. The Vector class is the running example here. Vector is simple to understand, provides range-checked access, and can report its size so you can loop over its elements. It has one serious flaw, though: it obtains memory for its elements with new, and nothing in the class gives that memory back. The destructor exists to close that gap.
C++ does define an interface for a garbage collector, but a programmer cannot count on one being present. Some environments do not have a collector, and sometimes a programmer wants precise control over when memory is freed. So the language needs another way to guarantee that memory obtained in a constructor is eventually released. That mechanism is the destructor.
Read the class in two halves. The constructor does the acquiring: it asks the free store for an array of s doubles, stores the pointer in elem, records the count in sz, and fills every slot with zero. The destructor does the releasing: it hands that same array back with delete[]. The two functions work as a pair. Whatever the constructor takes from the free store, the destructor is responsible for returning. The listing that follows is a simplified sketch of the core idea, and it leaves out error handling.
Vector(int s)is the constructor; it acquires the array- elem holds the pointer to the array on the free store
- ~
Vector()is the destructor; it releases the same array with delete[]
Users of Vector never call the destructor themselves. They create and use Vector objects much as they would variables of built-in types, and the cleanup happens without their intervention. The destructor runs as the object's lifetime ends, which, for a local Vector, is the closing brace of its block.
This arrangement is called the handle-to-data model. The Vector object itself is small and fixed in size: it holds a pointer and a count. The elements live elsewhere, on the free store, and the object refers to them through that pointer. The handle is the small object the program holds, and the data is the larger block it refers to. Because the destructor knows where that block begins through elem, it can release the whole block in one step.
Reading the Destructor Name
The destructor is named by a tilde followed by the class name. In the Vector example, the constructor is Vector(int s) and the destructor is ~Vector(). The constructor shares the class name exactly, and the destructor adds the tilde in front of that same name. The tilde is the mark that identifies the function as the destructor for that class.
The constructor and destructor form a matched pair: the constructor is named after the class, and the destructor is named after the class with a tilde in front. Whatever the constructor acquires, the destructor is responsible for releasing.
RAII and Leak Prevention
The constructor and destructor combination is the basis of many general resource management techniques in C++. The best-known is Resource Acquisition Is Initialization, usually abbreviated RAII. RAII is the technique of acquiring a resource in a constructor and releasing it in a destructor.
The payoff of RAII is that it lets you avoid what the source calls naked new operations and naked delete operations. A naked new is an allocation sitting in general-purpose code. A naked delete is a release written by hand in that same code. RAII keeps allocations buried inside the implementation of a well-behaved abstraction, such as Vector, so everyday code does not have to remember to clean up.
The reason this reduces leaks follows from the rules already traced. If the release is written as a destructor, it runs when the object's scope ends, however that scope is left. If the release is written by hand at the bottom of a function, a path that leaves the function earlier can skip it. Avoiding naked new and naked delete makes code far less error-prone and far easier to keep free of resource leaks.
Mistakes Beginners Make
Common mistake Assuming an object's lifetime starts when its name is first mentioned
Why it failsAn object must be constructed, meaning initialized, before it is used. Construction is the step that makes it usable.
Do this insteadIdentify the construction point first, then trace use from there.
Common mistake Expecting a new object to be destroyed at the closing brace of the block that created it
What it looks likeA function creates an object with new and returns; the object is still alive afterward.
Why it failsAn object created with new is not tied to the block where the new expression appears. It lives until delete is called.
Do this insteadFor dynamic objects, look for the matching delete, not the closing brace.
Common mistake Calling the destructor yourself on a scope-based object
Why it failsUsers of Vector never call the destructor themselves; cleanup happens as the object's lifetime ends.
Do this insteadLet the scope end the lifetime of scope-based objects.
Common mistake Writing a constructor that acquires memory with no destructor to release it
Why it failsNothing in the class gives the memory back, which is the flaw the destructor exists to close.
Do this insteadPair every acquisition in a constructor with a release in the destructor.
Common mistake Relying on a garbage collector to free memory
Why it failsC++ defines an interface for a garbage collector, but you cannot count on one being present.
Do this insteadUse the destructor to guarantee release.
Practice: Predict the Lifetime
A function declares a Vector v with three elements, uses it, and returns. Separately, the same function creates a Vector with new and returns a pointer to it. State when the destructor runs for each object.
Hints
- The first Vector is scope-based, so its end point is the closing brace of the function body.
- The second Vector is created with new, so its end point is the matching delete, which the caller controls.
Key Takeaways
- An object must be constructed before it is used, and its life ends at a point that depends on how it was created.
- A scope-based object is destroyed at the closing brace of the block where it was declared, while an object created with new lives until delete is called.
- A member object inherits the end point of the object that contains it, and a namespace object is destroyed at the end of the program.
- A constructor acquires a resource and the destructor, named with a tilde followed by the class name, releases it.
- RAII ties acquisition to constructors and release to destructors, which avoids naked new and naked delete and reduces resource leaks.