CodeMIRAI
0 XP

Constructors

3 min read 5 runnable examples Quiz included

A new object should be ready to use the moment it exists. A constructor is a special member function that runs automatically when an object is created, and its job is to set the object up.

Writing constructors

C++
#include <iostream>
using namespace std;

class Point {
public:
    double x, y;

    Point() : x(0), y(0) {}                        // default constructor
    Point(double x, double y) : x(x), y(y) {}      // takes the starting values

    void print() const { cout << "(" << x << ", " << y << ")" << endl; }
};

int main() {
    Point a;              // calls Point()
    Point b(3, 4);        // calls Point(double, double)
    Point c{1.5, -2};     // the same, with brace initialisation
    a.print();
    b.print();
    c.print();
    return 0;
}
Output
(0, 0)
(3, 4)
(1.5, -2)

A constructor has the same name as the class and no return type, not even void. A class can have several, and the arguments choose one, exactly like overloaded functions.

The part after the colon, : x(x), y(y), is the member initializer list. It gives each member its starting value as the member is created, which is preferred over assigning inside the body. In x(x), the outer x is the member and the inner one the parameter.

Constructors that check their input

C++
#include <iostream>
using namespace std;

class Temperature {
    double celsius;
public:
    explicit Temperature(double c) : celsius(c < -273.15 ? -273.15 : c) {}
    double get() const { return celsius; }
    double fahrenheit() const { return celsius * 9 / 5 + 32; }
};

int main() {
    Temperature t(25);
    cout << t.get() << "C = " << t.fahrenheit() << "F" << endl;
    Temperature impossible(-300);
    cout << impossible.get() << endl;     // clamped to absolute zero
    return 0;
}
Output
25C = 77F
-273.15

Because the only way to make a Temperature goes through the constructor, no Temperature can ever hold an impossible value. explicit stops C++ from quietly turning a plain number into a Temperature where a function expects one.

Default member values

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

class Settings {
public:
    int volume = 50;
    bool darkMode = false;
    string language = "en";

    Settings() = default;                 // keep every default
    Settings(int v) : volume(v) {}        // change one, keep the rest
};

int main() {
    Settings a;
    Settings b(80);
    cout << a.volume << " " << a.darkMode << " " << a.language << endl;
    cout << b.volume << " " << b.darkMode << " " << b.language << endl;
    return 0;
}
Output
50 0 en
80 0 en

Values written next to the members are used by every constructor that doesn't set that member itself. = default asks the compiler to write the do-nothing constructor for you.

Destructors

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

class Noisy {
    string name;
public:
    Noisy(const string& n) : name(n) { cout << "  hello from " << name << endl; }
    ~Noisy() { cout << "  goodbye from " << name << endl; }
};

int main() {
    cout << "start" << endl;
    Noisy a("a");
    {
        Noisy b("b");
        cout << "inner block ends" << endl;
    }
    Noisy c("c");
    cout << "main ends" << endl;
    return 0;
}
Output
start
  hello from a
  hello from b
inner block ends
  goodbye from b
  hello from c
main ends
  goodbye from c
  goodbye from a

The destructor, ~Noisy(), runs automatically when an object's lifetime ends: at the end of the block it was created in. Objects are destroyed in the reverse order they were made, so c goes before a.

This is one of C++'s most useful ideas, often called RAII: a constructor grabs a resource (memory, a file, a lock) and the destructor releases it, so it's released however the block ends. string, vector and file streams all clean up after themselves this way.

Copying objects

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

class Book {
public:
    string title;
    int pages;
    Book(const string& t, int p) : title(t), pages(p) {}
};

int main() {
    Book a("Dune", 412);
    Book b = a;                  // a copy: b has its own title and pages
    b.title = "Dune Messiah";
    b.pages = 256;
    cout << a.title << " (" << a.pages << ")" << endl;
    cout << b.title << " (" << b.pages << ")" << endl;
    return 0;
}
Output
Dune (412)
Dune Messiah (256)

Copying an object copies each member, so b is independent of a. For classes built from string, vector and plain values that's exactly right. A class that manages raw memory itself needs its own copy constructor, but using standard containers instead means you rarely have to write one.

Finished this lesson?Mark it complete to earn +20 XP and move your progress on.