Constructors
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
#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;
}(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
#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;
}25C = 77F
-273.15Because 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
#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;
}50 0 en
80 0 enValues 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
#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;
}start
hello from a
hello from b
inner block ends
goodbye from b
hello from c
main ends
goodbye from c
goodbye from aThe 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
#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;
}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.