Constructors and Destructors

Learn about special functions we can add to our classes, control how our objects get created and destroyed.
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a blacksmith character
Ryan McCombe
Ryan McCombe
Updated

With built-in types, such as int and string, we’ve previously seen how we can set initial values when creating objects:

int Health { 100 };
string Name { "Roderick" };

In this lesson, we’ll also learn how to give our user-defined types this ability:

Monster Goblin { "Basher the Goblin" };

We unlock this by adding constructors to our class.

Constructors

A constructor in C++ is a special member function of a class that is executed whenever we create new objects of that class.

It initializes the object's properties and can set up essential prerequisites for the object's functionality.

A constructor that takes no arguments is known as a default constructor.

This type of constructor is used when we want to create an object but don't need to specify any initial values for its properties.

As we’ve seen, we’ve already been able to create our objects without any arguments:

Monster Goblin;

This is because our classes are provided with a basic, default constructor as standard. However, we can replace this, to implement any code we need for our use case.

Let's see a basic example of a default constructor in action:

#include <iostream>
using namespace std;

class Monster {
 public:
  Monster() {
    cout << "Ready for Battle!";
  }

 private:
  string mName;
};

int main() {
  Monster Goblin;
}
Ready for Battle!

Class Variable Naming Conventions

In the previous example, we prefixed our class variables with the letter m, as in mName. This is an abbreviation of "member".

Adopting a naming convention for class members, like prefixing with m, can be beneficial for several reasons.

This is especially useful when we’re going to create constructors that accept parameters, as it prevents confusion between the parameters and the class members they are meant to initialize.

For example, to make our constructor clear for consumers, we’d want a parameter that initializes the object’s name to be called Name.

But if the class member is also called Name, that can get quite confusing, especially in the constructor that’s trying to set Name (in our class) equal to Name (from the parameter)

So, it’s somewhat common to implement some standard naming conventions for our private internal members, such as prefixing them with m.

Other common naming conventions include using an underscore (_) as a prefix (e.g., _name) or a postfix (e.g., Name_). These conventions all serve the same purpose

Constructor Parameters

Like other functions, constructors can be designed to take arguments, which we can use as needed within our constructor function body.

Typically, this is used to allow developers to pass values directly to the object's properties. For instance, a constructor that accepts a single argument can be used to set a specific property of a class.

Below, we’ve created a constructor that takes a string argument, granting the ability for our monster’s name to be set at creation time:

#include <iostream>
using namespace std;

class Monster {
public:
  Monster(string Name){
    mName = Name;
    cout << mName << " Ready for Battle!";
  }

private:
  string mName;
};

int main(){
  Monster Goblin{"Bonker"};
}
Bonker Ready for Battle!

Similar to other types of functions, a constructor can have multiple parameters, separated by a comma.

When calling the constructor (by declaring an object of its type) we also comma-separate the arguments:

#include <iostream>
using namespace std;

class Monster {
public:
  Monster(string Name, int Health){
    mName = Name;
    mHealth = Health;
    cout << mName << " Ready for Battle!"
      << "\nHealth: " << mHealth;
  }

private:
  string mName;
  int mHealth;
};

int main(){
  Monster Goblin{"Bonker", 150};
}
Bonker Ready for Battle!
Health: 150

Multiple Constructors

Our classes can define multiple constructors, allowing our objects to be created with a variety of different argument lists.

Below, we allow consumers of our class to create our objects either by providing a string representing the monster’s name, or both a string and an int representing their name and initial Health value:

#include <iostream>
using namespace std;

class Monster {
 public:
  Monster(string Name) {
    mName = Name;
    mHealth = 150;
    cout << mName << " Ready for Battle!"
         << "\nHealth: " << mHealth;
  }

  Monster(string Name, int Health) {
    mName = Name;
    mHealth = Health;
    cout << mName << " Ready for Battle!"
         << "\nHealth: " << mHealth;
  }

 private:
  string mName;
  int mHealth;
};

int main() {
  Monster Bonker{"Bonker"};
  cout << '\n';
  Monster Basher{"Basher", 250};
}
Bonker Ready for Battle!
Health: 150
Basher Ready for Battle!
Health: 250

Just like other functions, constructors can have optional parameters. This allows multiple argument lists to be supported by a single constructor.

Our previous code can, and should, be simplified to this:

#include <iostream>
using namespace std;

class Monster {
public:
  Monster(string Name, int Health = 150){
    mName = Name;
    mHealth = Health;
    cout << mName << " Ready for Battle!"
      << "\nHealth: " << mHealth;
  }

private:
  string mName;
  int mHealth;
};

int main(){
  Monster Bonker{"Bonker"};
  cout << '\n';
  Monster Basher{"Basher", 250};
}
Bonker Ready for Battle!
Health: 150
Basher Ready for Battle!
Health: 250

Ambiguous Constructor Calls

When defining multiple constructors, we need to ensure that they don’t "overlap". Specifically, any time we create an object, there must be only one constructor that supports the argument list that was provided.

Below, we have two constructors that both accept a single int parameter.

This is invalid because, if someone tries to instantiate our class with a single int argument, the compiler has no way of knowing what constructor is supposed to be used:

#include <iostream>
using namespace std;

class Monster {
public:
  Monster(int Level){ mLevel = Level; }
  Monster(int Health){ mHealth = Health; }

private:
  int mLevel;
  int mHealth;
};

int main(){
  // Which Constructor?
  Monster Bonker{10};
}

When we fall foul of this requirement, we will typically get a compiler error at the point where our class is defined:

error: 'Monster::Monster(int)': member function
already defined or declared

In some situations, our class definition may be valid, but we’ll get an error if we try to create an object with an argument list that can be handled by multiple constructors:

error: 'Monster::Monster': ambiguous call to
overloaded function

Default Constructor

Previously, we’ve seen how we were able to create an object from our class, providing no arguments at all:

Monster Basher;

This is because, out of the box, our classes come with a default constructor.

However, once we define a custom constructor, the default is automatically deleted.

If we want to allow our objects to be created without arguments, we can simply reimplement the default constructor. We do this by providing a constructor that takes no arguments:

class Monster {
 public:
  // A default constructor
  Monster() {
    // ...
  };

  Monster(string Name, int Health = 150) {
    mName = Name;
    mHealth = Health;
    cout << mName << " Ready for Battle!"
         << "\nHealth: " << mHealth;
  }

 private:
  string mName;
  int mHealth;
};

If we don’t need to provide any implementation for this, we can use the = default syntax to restore the original default constructor:

class Monster {
 public:
  Monster() = default;

  Monster(string Name, int Health = 150) {
    mName = Name;
    mHealth = Health;
    cout << mName << " Ready for Battle!"
         << "\nHealth: " << mHealth;
  }

 private:
  string mName;
  int mHealth;
};

Constructor prototypes

Similar to other functions, we can declare and define constructors in different locations. The syntax is exactly the same as we covered in the previous lesson:

class Monster {
 public:
  // The prototype
  Monster(int Health);

 private:
  int mHealth{150};
};

// The definition
Monster::Monster(int Health){
  mHealth = Health;
}

Destructor

A destructor in is another special member function of a class, complementary to the constructor.

It is called automatically when an object is deleted.

The syntax for a destructor is similar to the constructor but with a tilde (~) prefix. Here's a simple example:

class Monster {
 public:
  // Destructor
  ~Monster() {
    // ...
  }
};

When objects get destroyed, and the object lifecycle in general, will get more important as we get into more advanced topics.

For now, we can note that one scenario where objects will get destroyed is when the scope they were created in gets destroyed.

We can see an example of this below, where our Goblin is created within our function, and then automatically destroyed when our function ends:

#include <iostream>
using namespace std;

class Monster {
public:
  // Constructor
  Monster(){
    cout << "Monster Created\n";
  }

  // Destructor
  ~Monster(){
    cout << "Monster Destroyed\n";
  }
};

void SomeFunction(){
  Monster Goblin;
}

int main(){
  cout << "Hello World\n";
  SomeFunction();
  cout << "Goodbye!";
}
Hello World
Monster Created
Monster Destroyed
Goodbye!
Test your Knowledge

Constructors and Destructors

Imagine we have a class defined as follows:

class Robot {
public:
  Robot(string Model, int Year = 2024) {
    mModel = Model;
    mYear = Year;
  }

private:
  string mModel;
  int mYear;
};

What will be the result of creating a Robot object with the statement Robot MyRobot("RX100");?

Given the following class definition:

class Creature {
public:
  Creature(string name) {
    mName = name;
  }

  Creature() {
    mName = "Unknown";
  }

private:
  string mName;
};

What happens if you create an object of Creature class without passing any arguments?

What happens when you define a custom constructor in a class without explicitly defining a default constructor?

Consider the following class definition:

class Weapon {
public:
  Weapon(int Durability){
    mDurability = Durability;
  }
  Weapon(int Weight){
    mWeight = Weight;
  }

private:
  int mDurability;
  int mWeight;
};

What will be the effect of creating a Weapon object with the statement Weapon IronSword{500};?

What is a destructor?

Summary

As we conclude this lesson, let's recap the key concepts we've covered:

  • Understanding Constructors and Destructors: We've learned about constructors, special functions in a class that are called when objects are created. We've seen how these can be used to initialize objects with specific values and behaviors.
  • Default and Custom Constructors: We explored how default constructors work and how we can create custom constructors to meet specific needs.
  • Naming Conventions for Class Variables: The reason we might want to adopt class member naming conventions like prefixing member variables (e.g., mName).
  • Constructor Parameters: We looked at how constructors can take parameters to initialize objects with specific values.
  • Handling Multiple Constructors: We delved into creating classes with multiple constructors, allowing for flexible object creation.
  • Avoiding Ambiguous Constructor Calls: We learned the importance of ensuring constructors are not ambiguous to avoid compilation errors.
  • Implementing and Restoring Default Constructors: We discussed how to implement custom default constructors and how to restore the original default constructor using = default.
  • Destructor Introduction: We introduced destructors, which are called when objects are destroyed, and their role in resource management.

Next Lesson: Structs and Aggregate Initialization

In our next lesson, we will dive into the world of structs and aggregate initialization in C++. Here's what you can expect:

  • Understanding Structs: We'll explore what structs are in C++, how they differ from classes, and why they are used.
  • Struct Syntax and Usage: We'll cover the syntax of defining structs and how to use them effectively in your programs.
  • Comparing Structs and Classes: An examination of the differences and similarities between structs and classes will help you understand when to use each.
  • Basics of Aggregate Initialization: You'll learn about aggregate initialization, a method to initialize objects concisely and clearly.
  • Practical Examples: As always, we'll provide examples to illustrate these concepts, ensuring you can apply them in your programming projects.

Was this lesson useful?

Next Lesson

Structs and Aggregate Initialization

Discover the role of structs, how they differ from classes, and how to initialize them without requiring a constructor.
3D art showing a female character
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Constructors and Destructors

Learn about special functions we can add to our classes, control how our objects get created and destroyed.

3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Classes and Structs
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 60 Lessons
  • Over 200 Quiz Questions
  • 95% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Structs and Aggregate Initialization

Discover the role of structs, how they differ from classes, and how to initialize them without requiring a constructor.
3D art showing a female character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved