Constructors
A constructor is used to create an instance of a class (i.e., an object). It is a special kind of function with a special job.
In C++ there are a few different kinds of constructors that are used for different purposes. In this lesson, we'll discuss a couple of particularly important ones.
The Constructor's Job
The constructor's job is to initialize the object and make it ready for use. The memory the constructor is initializing has already been allocated (e.g., at the opening curly brace of the function), but until the constructor runs, the memory has not yet been initialized.
Thus, the constructor's main job is to initialize the data members of the object being constructed. (Although it can do other things as well.)
Parameterized Constructors
A parameterized constructor is exactly what it sounds like: a constructor that has parameters.
These constructors should look pretty familiar; for example, the highlighted code in the following snippet from
cow.hpp
:
class Cow {
public:
// We can only have a Cow if we know
// how many spots it has and how old it is
Cow(int numSpots, int age);
Cow() = delete;
...
A parameterized constructor must be explicitly invoked. For example, here are two highlighted lines from main.cpp
that invoke the parameterized constructor of Cow
:
int main() {
Cow bessie{3, 12};
const Cow mabel{1, 2};
Hay, what's up with the curly braces there? I'm used to parentheses.
Good catch! Hold onto that question and we'll get to it in a page or two.
The Default Constructor (No Parameters)
A constructor with no parameters has a special name: the default constructor.
This constructor can be invoked explicitly as
Sheep mead{};
Default Initialization Uses the Default Constructor
But also, and maybe more importantly, the default constructor is the constructor used for default initialization. For instance,
Sheep mead;
would also invoke the default constructor of class Sheep
to initialize mead
.
Note that
mead
is not “uninitialized” or “null
” or “none
” or anything you've seen in another language!Yeah, I'm right here! I'm not “none”! I'm some!
It also doesn't have an indeterminate value like a default-initialized
int
would.The variable
mead
contains an actual object that was initialized using a specific constructor: the constructor with no parameters.The default constructor.
Default initialization comes up in many contexts and sometimes it can be subtle. For example,
Sheep herd[10];
creates an array of ten Sheep
objects. How are they initialized? Using the default constructor!
So that one line of code actually calls Sheep
's default constructor ten times.
The Default Constructor Can Be Automatically Synthesized
For convenience (unless we tell C++ otherwise), every class has a default constructor, even if you don't define one!
Really? What does it do??
It does the most reasonable thing for a class with no explicitly defined constructor.
If you don't define a constructor, the compiler synthesizes a default constructor that initializes all member variables exactly as shown in the class definition. So if we didn't specify any initialization in the class definition,
- Member variables that hold primitive types (such as
int
,double
, etc.) have indeterminate values. - Member variables that hold objects of class type are initialized using their default constructors.
We can avoid this potentially dangerous issue for primitive types by specifying an initialization for member variables when we declare them in the class definition. For example, in our Cow
class, we could have written
...
private:
// The number of spots and an age can't be negative...
int numSpots_ = 4;
int age_ = 0;
};
...
Cool!
Yeah, it's pretty handy. Except…
When You Don't Want a Default Constructor
If you don't want a default constructor, it isn't enough to just not define one, because in that case C++ will synthesize one automatically. Instead we must use some new syntax—= delete
—to tell C++ that the class should not have a default constructor.
Here it is highlighted in our code
class Cow {
public:
// We can only have a Cow if we know
// how many spots it has and how old it is
Cow(int numSpots, int age);
Cow() = delete;
...
In this context, specifying = delete
tells the compiler to give an error if we write any code that tries to use this function.
So it's like it doesn't exist.
Pretty much.
But wait, why would we want to get rid of a function? If C++ will automatically write a default constructor for us, why not just let it?
Yeah, I want MORE constructors!!
Well, sometimes it just doesn't make sense to default initialize. Maybe we don't know what the “default” Cow should be. Maybe we really need to be given the age and number of spots in order to create a Cow.
And sometimes C++'s automatically synthesized code wouldn't be correct. Using
= delete
says to C++, “I know you want to help, but not this time!”
In our running example, we can see that the default constructor for Cow
has been effectively removed.
Because we removed the default constructor, the following code would no longer be allowed:
Cow buttercup; // Would cause a compile error!
Cow herd[5]; // Would cause a compile error!
There are more special kinds of constructors, but we'll leave those for another time.
Check-In Questions
Why did they choose
= delete
? Why not= disable
or= deny
or something?I already had
delete
as a keyword in the language, so it seemed easiest to just reuse it here.Oh Bjarnie, you're such a character!
(When logged in, completion status appears here.)