Constructors and destructors

Introduction

A constructor is a member function with the class's own name and no return type. It runs automatically when an object is created, so an object is never left half set up. A destructor, ~ClassName(), runs automatically when the object dies.

An object should be ready to use from the moment it exists, and should clean up after itself when it goes. Constructors and destructors make both of those happen automatically, without any call you could forget.

How it works

Constructors

#include <iostream>
using namespace std;

class Point {
    int x, y;
public:
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}
    void print() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

int main() {
    Point origin;
    Point p(3, 4);
    Point q = Point(-1, 2);
    origin.print();
    p.print();
    q.print();
    return 0;
}
Open in compiler
  • Point() is the default constructor: it runs for Point origin;.
  • Point(int x, int y) runs for Point p(3, 4);. Several constructors overload each other, like any functions.
  • : x(x), y(y) is the member initializer list: it gives each member its first value before the body runs. In x(x), the outer x is the member and the one in brackets is the parameter.

If a class declares no constructor at all, the compiler writes a default one that leaves int and double members uninitialized (garbage, as in C). As soon as you write any constructor, that free default one is gone: Point origin; then needs a Point() of your own.

Syntax

class Box {
    int size;
public:
    Box() : size(0) {}             // default constructor
    Box(int s) : size(s) {}        // constructor with a parameter
    ~Box() { /* release resources */ }
};

Example

#include <iostream>
#include <string>
using namespace std;

class Tracker {
    string name;
public:
    Tracker(string n) : name(n) {
        cout << "built " << name << endl;
    }
    ~Tracker() {
        cout << "destroyed " << name << endl;
    }
};

int main() {
    Tracker a("a");
    {
        Tracker b("b");
        Tracker c("c");
    }
    Tracker d("d");
    return 0;
}
Open in compiler

A local object is destroyed when its block ends, at the closing }. Objects in the same block die in reverse order of creation: c before b. Watch the call stack: Tracker::Tracker frames appear as objects are made, and Tracker::~Tracker frames when they die. d and a are destroyed when main ends.

Destructors matter for objects that own something: memory from new, an open file, a network connection. The destructor releases it, so the object cleans up after itself, however the block is left. This idea is called RAII (resource acquisition is initialization), and it is how string and vector manage their memory.

Line by line

  1. Tracker stores a name, and its constructor uses the initializer list : name(n) to set it.
  2. The constructor prints built, and the destructor ~Tracker() prints destroyed.
  3. a is created first, at the top of main.
  4. The inner block creates b and then c. At its closing }, they are destroyed in reverse order: c first, then b.
  5. d is created after the block ends.
  6. When main returns, d and a are destroyed, again newest first.

Expected result

built a
built b
built c
destroyed c
destroyed b
built d
destroyed d
destroyed a

In the real world

File streams close their file in their destructor, and locks in multithreaded code are released by a destructor. This is why C++ code rarely needs a "finally" step: leaving the block is enough to clean up.

Common mistakes

  • Writing Point p(); to call the default constructor: it declares a function named p. Write Point p;.
  • Giving a constructor a return type, even void.
  • Initializing members in the wrong order: they are initialized in the order they are declared in the class, not the order in the list.

Best practices

  • Initialise every member in the initializer list.
  • Write the initializer list in the same order as the members are declared.
  • Give a class a destructor whenever it owns something that must be released.

Summary

Constructors set up an object as it is created, ideally with a member initializer list, and destructors clean up when it is destroyed. Local objects die at the end of their block in reverse order, which is the basis of RAII.

Quick check

1. Objects a, b, c are created in that order in one block. In what order are they destroyed?
2. A class has only Point(int x, int y). Does Point p; compile?
3. What is the destructor of class Box called?

Key takeaways

  • A constructor makes sure an object is never half set up.
  • Local objects are destroyed at the end of their block, newest first.
  • A destructor releases what the object owns.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.