Copy and Move Construction
Copy and move declarations are selected by the same rule as every other declaration: the argument list decides which constructor runs.
Where an Object's Starting Values Come From
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. This article answers two questions: how a class arranges for that starting value to be set at the moment an object comes into existence, and why that arrangement matters for correctness.
What do you think happens?
Given the declaration complex r(-4.0, 3.0), what real part and imaginary part does r hold right after it is declared?
Reveal answer
Real part -4.0 and imaginary part 3.0
The two arguments are assigned in order: the first becomes the real part and the second becomes the imaginary part. Reversing the arguments in the declaration reverses the stored parts.
What a Constructor Does
A constructor is a member function that initializes objects of a class, and it is selected by the arguments supplied at declaration.
The mechanism is called a constructor. A constructor has the same name as the class and has no return type written in front of it. Its job is to give the object its starting state. Within a constructor, the member initializer list after the colon is what assigns incoming values into the stored parts. In the example below, the list :re{r}, im{i} assigns the parameter r into the member re and the parameter i into the member im.
- The constructor has the class name complex and no return type.
- The parameters r and i are placeholders for the values passed in, not the stored members.
- The initializer list after the colon assigns r into re and i into im.
Matching an Argument List to a Constructor
A class can define several constructors with the same name. 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. Choosing a constructor is therefore a matching step: the compiler looks at the argument list of the declaration and selects the constructor whose parameter list fits it.
Selecting a constructor from three declarations
For each declaration, which constructor is chosen and why? complex a(1.0, 2.0); complex b; complex c(a);
- 1Count and inspect the arguments in complex a(1.0, 2.0)
Two numeric arguments are supplied, so the matching constructor is the one that takes two numbers,
complex(double, double). - 2Inspect the arguments in complex b
No arguments are supplied. The constructor invocable without an argument is the default constructor,
complex(), and it is the one chosen. - 3Inspect the arguments in complex c(a)
The single argument is an existing complex object rather than a number. The compiler does not pick the two-number constructor here. It selects the constructor whose parameter list accepts a complex object, which is the form the copy and move rules govern.
a uses complex(double, double), b uses complex(), and c is selected by its argument of type complex rather than by any numbers.
Tracing Values Through Construction
The sequence below follows three declarations in order. Each declaration sends a request to the constructor that matches its argument list, and the constructor then writes values into the object's stored parts. Read the messages top to bottom and track the value each object holds after each step.
Step 2 shows p with real part 3.0 and imaginary part -4.0. Step 4 shows q holding {0,0}, because a declaration with no arguments starts at {0,0}. Step 6 shows r with real part -4.0 and imaginary part 3.0. Each object's values come entirely from its own argument list, so the sequence never mixes the values of one declaration into another.
How Uninitialized Parts Become Defined Values
Before a constructor runs, the stored parts of a new object do not yet hold a value from the program. The constructor fills them in. The before-and-after view below shows the two stored parts of a complex object declared as complex a(1.0, 2.0), with the state of each part before and after construction.
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. So a default constructor can sit among other constructors that take arguments; what makes complex() the default is simply that a caller can write the declaration with nothing supplied.
| Feature | Default constructor | Constructor taking arguments |
|---|---|---|
| How it is called | Declaration with nothing supplied, such as complex q; | Declaration with matching arguments, such as complex p(3.0, -4.0); |
| Example in the complex class | complex() | complex(double, double) |
| Starting value of the stored parts | Every argument-free declaration starts at {0,0} | Stored parts take the values of the arguments |
| Selected by | Absence of arguments | Matching the argument list |
How the default constructor differs from constructors that take arguments
Defining a default constructor eliminates the possibility of uninitialized variables of that type. Any variable of type complex that is declared without arguments starts with a defined value, so code that reads its parts does not depend on luck.
Copy and Move Declarations by Argument Form
Copy and move declarations are selected by the same rule as every other declaration: the argument list decides which constructor runs. The source pack for this article establishes that rule and the default constructor's guarantee; it does not give the full details of how a copy or move constructor is written. What you can rely on is the selection principle. A declaration that supplies an existing complex object as its argument, such as complex c(a) or complex c(std::move(a)), is matched by that argument form, not by the two-number constructor.
Mistakes Beginners Make with Constructors
Common mistake Writing a return type in front of a constructor
What it looks likevoid complex(double r, double i) : re{r}, im{i} {}Why it failsA constructor has the class name and no return type written in front of it.
Do this insteadcomplex(double r, double i) : re{r}, im{i} {}Common mistake Treating the parameter names as the stored members
What it looks likecomplex(double r, double i) : r{r}, i{i} {}Why it failsThe parameters r and i are only placeholders. The stored members are re and im, and the initializer list is what assigns values into them.
Do this insteadcomplex(double r, double i) : re{r}, im{i} {}Common mistake Thinking the default constructor is the one that takes arguments
Why it failsThe default constructor is the one invocable without an argument, such as
complex(). Constructors that take arguments are selected when the declaration supplies those arguments.Do this insteadUse complex() for a declaration with nothing supplied, and complex(double, double) for one with two numbers.
Common mistake Passing arguments in the wrong order and then blaming the class for the wrong value
Practice Tracing a Construction
Using the complex class from this article, declare complex p(3.0, -4.0); then complex q; then complex r(-4.0, 3.0). For each declaration, name the constructor that runs and state the real and imaginary values the object holds. Then say which object would be the first to show an unexpected real part if you had meant r to have real part 3.0.
Hints
- Match each argument list to a constructor: two numbers select
complex(double, double), and no arguments selectcomplex(). - A declaration with no arguments starts at {0,0}.
- The real part of r is the first argument of its declaration, because the constructor assigns arguments in order to re and im.
Key Takeaways
- A constructor is a member function that initializes objects of a class, and it is selected by the arguments supplied at declaration.
- Constructors can be overloaded, and the compiler chooses among them by matching the argument list of each declaration.
- The default constructor is the one invocable without an argument, and defining it eliminates uninitialized variables of that type because argument-free declarations start at {0,0}.
- When an object holds an unexpected value, trace which constructor ran and what its arguments assigned, step by step.
- Copy and move declarations are selected by their argument form, and the source pack's rule is the same argument-matching principle used for every constructor.