Destructors and Ending an Object's Lifetime
A class can control the lifetime of the resource it holds by defining constructors, copy operations, move operations, and a destructor.
A Million Doubles and a Question
Imagine a program that builds a Vector holding a million double values. At some point that vector has to leave the scope where it was created and arrive in a different scope, where another part of the program will use it. The question that matters is what happens to those million values during that trip. Do they get rebuilt, duplicated, or handed over?
What do you think happens?
When a vector of a million doubles is handed from one scope to another by a move, are the million elements copied?
Reveal answer
No, the elements are not duplicated. The resource is handed over to the receiving object.
A move transfers responsibility for the resource rather than creating a second, independent copy of its contents. Duplicating a million elements is the expensive operation that a move avoids.
The rest of this article follows that question through the lifecycle of an object. You will see how a class decides what happens to the resource it holds when the object is created, when it is copied, when it is moved, and when it finally goes out of use.
Why a Class Governs Its Own Resource
Many classes hold something that is expensive or unusual to own. Examples from the source include a block of memory, a container full of elements, and a handle to an activity running alongside the rest of the program. Every such holding has a beginning, a period of use, and an end.
A class controls the lifetime of the resource it holds when it explicitly decides how that resource is created, how it may be duplicated, how it may be handed over, and when it is finally released. A class author does this by defining constructors, copy operations, move operations, and a destructor.
The central question is simple to state: when an object moves between places in a program, or when it goes out of use, what should happen to the resource it is holding? The answer depends on the resource. Some resources can be duplicated without trouble, some should not be duplicated because doing so is wasteful, and some cannot be duplicated at all. The language gives the class author a set of operations for expressing which case applies.
The purpose of these tools is to make those decisions explicit, instead of leaving them to chance. A class that does not state what happens to its resource at each stage leaves the behavior to whatever default happens to apply.
The Four Lifetime Operations
A class author can take full control over the lifetime of a contained resource by defining four kinds of operation. Taken together, these four operations are the complete set of hooks the class author has for deciding what happens to the resource at every stage.
- Constructors: set up an object and establish the resource it holds
- Copy operations: create a new object that duplicates an existing one
- Move operations: transfer an object's contents somewhere else
- Destructor: runs when the object's lifetime comes to an end
The four operations are not independent of one another. Each one answers a different question about the same resource. The constructor answers how the resource begins, the copy operations answer how it may be duplicated, the move operations answer how it may be handed over, and the destructor answers when it is released. The diagram below shows how these four hooks surround the held resource.
Copying Versus Moving
Of the four operations, the move operations are the focus of this topic, because they answer a question the copy operations cannot answer cheaply. Copying is the operation that creates a second, independent object. Moving is the operation that relocates the resource itself, so that the receiving object takes over what the original was holding.
Both operations move an object's value from one place in the program to another, but they differ in what they must do with the underlying resource. The table below summarizes that contrast using only what the source supports.
| Question | Copy | Move |
|---|---|---|
| What is created? | A second, independent object | The receiving object takes over the resource |
| Is the resource duplicated? | Yes, its contents are duplicated | No, the contents are not duplicated |
| Cost for a large vector | Expensive, since every element is duplicated | Cheap, since the resource is handed over |
| Who is responsible afterwards? | Both objects hold their own copy | The receiving object is responsible for the resource |
Copy versus move, based on the source's description of the two operations
Objects That Cannot or Should Not Be Copied
Some objects cannot be copied at all, and some can be copied but should not be, because the copy would be too costly. The source gives two concrete examples. Both are useful for deciding when a move is the right choice.
Two Objects, Two Reasons to Move
A program holds a standard-library thread and a Vector of a million doubles. For each object, decide whether it should be copied or moved when it passes from one scope to another, and explain why.
- 1Examine the thread
A standard-library thread represents a concurrent activity. A thread cannot be copied, since there is no meaningful way to have two copies of one running activity. A copy is therefore not an available option, and the thread is a natural candidate for a move.
- 2Examine the vector
The Vector of a million doubles can be copied, but a full duplicate of a million elements is expensive. The programmer would rather avoid that cost, so a move is the preferred option.
- 3Compare the two reasons
The thread is moved because copying it is impossible. The vector is moved because copying it is wasteful. Both lead to the same decision, but the underlying reasons differ.
- 4Confirm the decision
Moving the thread transfers the concurrent activity to the new owner, and moving the vector transfers the existing million elements without rebuilding them. In both cases the resource is handed over rather than duplicated.
Both objects should be moved. The thread cannot be copied at all, and the million-element vector should not be copied because duplicating it is expensive.
Tracing a Move Between Scopes
A move constructor is the operation that lets an object move simply and cheaply from one scope to another. Its defining feature is cheapness: the object's resource is handed over rather than rebuilt. Follow the vector from the previous section through a move.
Before the move, the source scope is the one whose object holds the million doubles. The move constructor runs, and the destination scope ends up holding the vector. The point to notice is that the elements do not have to be duplicated along the way, which is exactly what made copying undesirable in the first place.
Notice what the diagram does not contain: a second group of a million doubles. That absence is the whole point. A move leaves the contents where they are and changes which object is responsible for them. A copy, by contrast, would produce a second set of elements, which is the cost the programmer wanted to avoid.
Following an Object From Creation to Destruction
An object's lifetime can be followed as a sequence of states. It begins when the constructor sets it up. It may then be copied, which produces a second independent object, or moved, which hands the resource to another object. Once the object's lifetime comes to an end, the destructor runs and releases the resource it holds.
The destructor is the last of the four operations. It runs when the object's lifetime comes to an end, which is when the object goes out of use at the end of its scope. Its job is to release the held resource. A source object that has been moved from no longer holds the resource, so its destructor has nothing to release, and it still runs safely.
Trace a single object through four stages: constructed and holding a resource, moved to a second scope, the moved-from object reaching the end of its scope, and the receiving object reaching the end of its scope. At each stage, state which object holds the resource and whether the resource has been released yet.
Hints
- After the move, ask which object is responsible for the million elements.
- The moved-from object's destructor releases nothing because it no longer holds the resource.
- The receiving object's destructor is the one that releases the resource when its scope ends.
Mistakes When Choosing Move or Copy
Common mistake Assuming a move duplicates the contents of the object
What it looks likeA vector of a million doubles is moved into a new scope and the programmer expects the new scope to have its own separate set of elements.
Why it failsA move transfers responsibility for the resource. It does not create a second, independent set of elements, which is the point of moving instead of copying.
Do this insteadTreat a move as a handover of the resource. After the move, the receiving object owns the same elements.
Common mistake Trying to copy an object that cannot be copied
What it looks likeA standard-library thread is copied in the same way as a plain number.
Why it failsA thread cannot be copied, since there is no meaningful way to have two copies of one running activity.
Do this insteadUse a move for a thread. The concurrent activity is transferred to the receiving object.
Common mistake Copying a large object out of habit
What it looks likeA large vector is copied each time it is passed to another scope, even though a move would avoid the cost.
Why it failsMaking a full duplicate of a million elements is expensive, and the source says a programmer would rather avoid it.
Do this insteadWhen the resource is large and does not need to be duplicated, prefer a move.
Common mistake Expecting a moved-from object to still hold its resource
Deciding Between Copy, Move, or Neither
The decision depends on the resource. The source describes three cases: some resources can be duplicated without trouble, some should not be duplicated because doing so is wasteful, and some cannot be duplicated at all. Those three cases map onto a practical way of thinking about the choice.
- If the resource can be duplicated cheaply, copying creates an independent object that is safe to use separately.
- If the resource is large and copying it is wasteful, prefer a move so the contents are handed over instead of rebuilt.
- If the resource cannot be duplicated at all, as with a standard-library thread, a move is the only option that transfers it.
Decide the behavior for each resource explicitly. Ask how the resource is created, whether it may be duplicated, whether it may be handed over, and when it is released. Writing those answers down in the class is what makes the lifetime of the resource a deliberate design rather than an accident.
Applying the Move Decision to New Cases
For each object below, decide whether it should be copied, moved, or whether the copy question does not apply. Justify each answer using the three cases from the source: duplicable cheaply, duplicable but wasteful, or not duplicable at all. (a) A standard-library thread passed to a helper function. (b) A Vector of a million doubles passed to a function that only needs to read it once and then discard it. (c) A small value object holding a few numbers that is handed to another scope.
Hints
- For the thread, ask whether two copies of one running activity can exist.
- For the large vector, ask whether duplicating a million elements is wasteful, and whether the original still needs to be kept.
- For the small value object, ask whether duplicating a few numbers is cheap.
Key Takeaways
- A class controls the lifetime of the resource it holds by defining constructors, copy operations, move operations, and a destructor.
- Copying creates an independent duplicate, while moving transfers responsibility for the resource, which avoids the cost of duplicating large contents.
- A move constructor lets an object move simply and cheaply from one scope to another, which matters for objects that cannot or should not be copied, such as a standard-library thread or a vector of a million doubles.
- A moved-from object is left in a valid but empty state, and its destructor still runs safely while releasing nothing.