Concepts / Destructors and Ending an Object's Lifetime

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.

  • Programming

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?

  • Yes, every element is duplicated

  • No, the resource is handed over instead of duplicated

Reveal answer
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.

Definition

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.

Held resourcememory, container, oractivityConstructorsets up the objectCopy operationscreate an independentduplicateMove operationstransfer the contentsDestructorruns at end of lifetimeestablishesduplicateshands overreleases
How do the four kinds of lifetime operations relate to one another and to the resource the class holds?

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.

QuestionCopyMove
What is created?A second, independent objectThe receiving object takes over the resource
Is the resource duplicated?Yes, its contents are duplicatedNo, the contents are not duplicated
Cost for a large vectorExpensive, since every element is duplicatedCheap, since the resource is handed over
Who is responsible afterwards?Both objects hold their own copyThe receiving object is responsible for the resource

Copy versus move, based on the source's description of the two operations

Original vectora million doublesOriginal vectora million doublesDuplicate vectora second million doublesReceiving objectsame elements, new ownerduplicated intohanded over to
Which parts of the data are duplicated in memory during a copy, and which are only reassigned during a move?

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.

Worked example

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.

  1. 1
    Examine 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.

  2. 2
    Examine 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.

  3. 3
    Compare 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.

  4. 4
    Confirm 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.

Result

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.

Source objectholds the vectorMillion doublesowned by the sourceSource objectempty, valid stateDestination objectnow holds the vectorMillion doublessame elements, no duplicateownsowns after move
After the move, where does the resource live, and what state is the source object left in?

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.

MEDIUM

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 like

    A 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 fails

    A 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 instead

    Treat 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 like

    A standard-library thread is copied in the same way as a plain number.

    Why it fails

    A thread cannot be copied, since there is no meaningful way to have two copies of one running activity.

    Do this instead

    Use 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 like

    A large vector is copied each time it is passed to another scope, even though a move would avoid the cost.

    Why it fails

    Making a full duplicate of a million elements is expensive, and the source says a programmer would rather avoid it.

    Do this instead

    When 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.
Best practice

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

MEDIUM

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.

Related concepts

More concepts starting with D