Concepts / Object Construction and Initialization

Object Construction and Initialization

An object's lifetime runs from its construction, which must finish before use, to its destruction.

  • Programming

Why Objects Need a Start and an End

Every object in a program has a beginning and an end. Before an object can be used safely, something has to bring it into existence and prepare it. Later, something has to remove it. The span of time between those two moments is called its lifetime. The question that matters most for writing correct programs is how you know when an object ends.

What do you think happens?

A named object is declared inside a block of code. Before reading on, what do you think ends its life: the last line that uses it, or something else?

  • The last line that uses it

  • The end of the scope it was declared in

  • The moment the program exits

Reveal answer
Answer:

The end of the scope it was declared in

For a named object, the end of its scope determines its point of destruction. The scope marks the boundary, and the boundary sets the clock.

The answer depends on two ideas that work together. The first is scope, which describes the region of a program where an object can be reached. The second is the set of rules that tie construction and destruction to that region. Once you can read those rules, you can predict the end of an object's life without running the program.

The Three Stages of an Object's Life

Think of an object's life as three stages. It begins with construction, the initialization step that prepares the object. Only after that step is complete should the object be used. The object then remains in use for as long as the program still has a reason to reach it. Its life ends with destruction, the cleanup step that removes it. Construction always comes before use, and destruction always comes after.

Constructionobject is preparedFirst usesafe only afterconstructionLast useprogram still has a reasonto reach itDestructioncleanup removes the objectthenremains in useends
In what order do an object's construction and destruction happen relative to its first and last use, and when exactly does it stop existing?

Notice that the order of this timeline is fixed. Destruction is not a choice the programmer makes at a random moment. It is the consequence of the scope ending. This is why scope is the first thing to check whenever you want to know how long something survives.

Working Out When an Object Ends

The end of an object's scope determines its point of destruction. That single rule is simple to state, but it produces different results depending on what kind of object you are looking at. Three cases are worth separating: named objects in a block, namespace objects, and members.

Tracing the Complex Number Fields

Every object of a class holds data, and that data has to start somewhere. Imagine a complex number class that stores a real part and an imaginary part. Before any arithmetic can happen, each stored part needs a value. A class arranges for that starting value through a constructor, which runs at the moment an object comes into existence.

cpp
Notes on the code
  • re and im are the stored members, the data each object holds
  • complex() takes no arguments, so it is the default constructor
  • The member initializer list after the colon assigns incoming values into the stored parts
  • The parameter names r and i are only placeholders for the values passed in

The class above is an illustrative sketch in the spirit of the complex class the source describes, with three constructors that share one name and differ only in their parameter lists. Use it to trace the values step by step.

Worked example

Constructing a complex object with one argument

Given the sketch class, what values does the object c hold after the declaration complex c(2.5); runs?

  1. 1
    Match the argument list

    The declaration supplies one argument, 2.5, so the one-parameter constructor complex(double r) is selected.

  2. 2
    Bind the parameter

    Inside that constructor, the placeholder r holds 2.5 for the duration of the initialization.

  3. 3
    Run the initializer list

    re{r} stores 2.5 into the real part. im{0} stores 0 into the imaginary part, because this constructor was written to supply that value.

  4. 4
    Check the result

    The object is ready for use with re equal to 2.5 and im equal to 0.

Result

re is 2.5 and im is 0. The imaginary value came from the constructor that was selected, not from the argument the caller wrote.

re2.5 from argument rim0 from constructor bodychoice
After the complex object is constructed, what is stored in each of its fields, and which step in construction put each value there?

When a value in a constructed object is not what you expected, trace which constructor ran and what it assigned. In this case the surprising zero in im does not come from the caller. It comes from the selected constructor's initializer list. If you had expected im to hold 2.5, the bug is in the chosen constructor, not in the declaration.

Constructors as Overloaded Entries

Definition

A constructor is a member function that initializes objects of a class, and it is selected by the arguments supplied at declaration.

Read the constructors in the sketch as entries in the same list. Each has the same name as the class, has no return type written in front of it, and differs only in its parameter list. The member initializer list after the colon is what assigns the incoming values into the stored parts. The parameter names are not the stored members themselves.

This is the feature called overloading: one name, several constructors, with the compiler choosing among them by the arguments present. The source states that constructors can be overloaded to accept different numbers and types of arguments, and the three complex constructors are a concrete instance of that rule.

Declaration ofcomplexarguments suppliedHow many arguments?match the parameter listcomplex()zero argumentscomplex(r)one argumentcomplex(r, i)two argumentschecknoneonetwo
Given a declaration with a particular argument list, which decision steps lead to choosing one constructor over the others?

The Default Constructor and Its Guarantee

A constructor that can be invoked without an argument is the default constructor. In the complex class that role belongs to complex(). The definition describes how a constructor is called, not what it looks like. A constructor can be the default one even though it sits among other constructors that take arguments. What makes complex() the default is simply that a caller can write the declaration with nothing supplied.

Definition

A default constructor is any constructor that can be invoked with no argument, so that a declaration supplying nothing still produces a fully initialized object.

Key point

Defining a default constructor eliminates the possibility of uninitialized variables of that type, because every argument-free declaration starts at {0,0}.

The benefit is a guarantee about the class as a whole. Any variable of type complex declared without arguments starts with a defined value, so code that reads its parts does not depend on luck. For the sketch class, a declaration such as complex z; yields re and im both equal to 0.

Mistakes When Reading Lifetimes and Constructors

  • Common mistake

    Assuming a block's end destroys every object declared in it

    What it looks like

    Creating an object with new inside a block and expecting it to be cleaned up when the closing brace is reached.

    Why it fails

    An object created with new lives until it is destroyed by delete, so it does not end when its block ends.

    Do this instead

    Track the matching delete for every new-created object, and reason from the scope only for named objects.

  • Common mistake

    Looking for a separate destruction point for a member

    Why it fails

    A member is part of a larger object, so its destruction follows the destruction of the object it belongs to.

    Do this instead

    Find the end of the owning object's lifetime, and the member ends with it.

  • Common mistake

    Confusing a parameter name with the stored member

    What it looks like

    Reading r in complex(double r) as if it were the real part stored in the object.

    Why it fails

    The parameter names are only placeholders for the values passed in. The stored parts are set by the initializer list.

    Do this instead

    Follow the initializer list, such as re{r}, to see which stored member receives the value.

  • Common mistake

    Blaming the caller for an unexpected field value

    What it looks like

    Writing complex c(2.5) and assuming im must have come from the argument list.

    Why it fails

    The constructor that was selected decides what each field receives. Here im was set by that constructor's initialization, not by the argument.

    Do this instead

    Identify which constructor matched the argument list, then read what that constructor assigns.

  • Common mistake

    Using an object before its construction has finished

Predicting Which Constructor Runs

What do you think happens?

Using the sketch class, which constructor runs for the declaration complex c; with no arguments supplied?

  • complex(double r)

  • complex(double r, double i)

  • complex()

Reveal answer
Answer:

complex()

No arguments are supplied, so the constructor that can be invoked with nothing is selected. That is the default constructor, which initializes re and im to {0,0}.

MEDIUM

A teammate writes complex d(1.5, -2.0); and reports that d.im is 0 instead of -2.0. Using the sketch class, work out which constructor should run and which initializer list line would produce the surprising result. Explain where in the construction the wrong value is set.

Hints
  • Count the arguments in the declaration and match them to a parameter list.
  • Check the initializer list of the selected constructor, not the declaration, for the value stored in im.
  • Is the selected constructor the two-argument one? If it is, its initializer list should store i into im.

Summary of Construction and Destruction

An object's lifetime runs from its construction, which must finish before use, to its destruction. Construction always comes before use, and destruction always comes after. Scope is the first thing to check when you want to know how long an object survives.

At a glance

  • An object's lifetime begins with construction, which must complete before use, and ends with destruction.
  • A named object is destroyed at the end of its scope, a namespace object at the end of the program, and a member along with the object it belongs to.
  • An object created with new lives until the program destroys it with delete, so the end of its block does not end it.
  • A constructor initializes an object, and the argument list selects which overloaded constructor runs.
  • A default constructor, which can be invoked with no argument, guarantees that argument-free declarations start at {0,0}.

Related concepts

More concepts starting with O