Constructors
In the last lesson Rohan's CricketPlayer object was created first and given a name afterwards. For a moment it existed without a name, and nothing stopped a programmer from forgetting that second step. A constructor fixes this: it is a special member function that runs automatically when an object is created, so every object starts life complete and valid.
In this lesson you will write constructors with member initializer lists, give a class several constructors, prevent unwanted conversions with explicit, and control copying.
Writing a constructor
A constructor has the same name as the class and no return type, not even void. The part after the colon is the member initializer list: it initialises each member directly, before the constructor body runs.
#include <print>
#include <string>
class Student {
public:
Student(const std::string& name, int roll)
: name_(name), roll_(roll) {
std::println("Created {}", name_);
}
void show() const {
std::println("{} has roll number {}", name_, roll_);
}
private:
std::string name_;
int roll_;
};
int main() {
Student a("Priya", 14);
Student b{"Kabir", 22}; // braces call the same constructor
a.show();
b.show();
// Student c; // error: no default constructor
}Created Priya Created Kabir Priya has roll number 14 Kabir has roll number 22
Once you write any constructor, the compiler no longer creates the empty default constructor for you, so Student c; fails. That is usually what you want: a student without a name makes no sense.
Why use the initializer list instead of assigning in the body (name_ = name;)? Assignment in the body first creates an empty string and then replaces it, which is wasted work. More importantly, const members and reference members cannot be assigned at all; they can only be initialised, so the initializer list is the only way.
Several constructors, defaults and delegation
A class can have several overloaded constructors. To avoid repeating code, one constructor can delegate to another (C++11). Members can also have default member initializers right where they are declared. This example also uses a static data member, which belongs to the class as a whole rather than to any single object.
#include <print>
#include <string>
class Ticket {
public:
Ticket() : Ticket("Unknown passenger") {} // delegates
explicit Ticket(const std::string& passenger, const std::string& coach = "Sleeper")
: passenger_(passenger), coach_(coach), number_(++issued_) {}
void show() const {
std::println("#{} {} in {}", number_, passenger_, coach_);
}
static int issued() { return issued_; }
private:
std::string passenger_;
std::string coach_;
int number_ = 0;
static inline int issued_ = 0; // one counter shared by all tickets
};
int main() {
Ticket a("Meera", "3A");
Ticket b("Farhan");
Ticket c;
a.show();
b.show();
c.show();
std::println("Tickets issued: {}", Ticket::issued());
}#1 Meera in 3A #2 Farhan in Sleeper #3 Unknown passenger in Sleeper Tickets issued: 3
The default constructor hands the work to the main constructor with a default name, so the numbering logic exists in one place only. static inline int issued_ = 0; (inline static members are C++17) creates a single counter for the whole class; a static member function such as Ticket::issued() can be called without any object.
explicit: no surprise conversions
A constructor that can be called with one argument also acts as an automatic conversion. Without protection, a plain number could silently turn into a Rupees object. Mark such constructors explicit:
#include <print>
class Rupees {
public:
explicit Rupees(long long amount) : amount_(amount) {}
long long value() const { return amount_; }
private:
long long amount_;
};
void pay_fee(Rupees fee) {
std::println("Paid Rs. {}", fee.value());
}
int main() {
pay_fee(Rupees{1500}); // clear and allowed
// pay_fee(1500); // error: constructor is explicit
}Paid Rs. 1500
Without explicit, pay_fee(1500) would compile, and so would pay_fee(roll_number) by mistake. A good rule: make every single-argument constructor explicit unless the conversion is truly natural.
Copy constructors, = default and = delete
When you write Report b = a;, C++ creates b with the copy constructor. The compiler writes one for you that copies every member, which is right for most classes. You can write your own to see or change what happens:
#include <print>
#include <string>
class Report {
public:
explicit Report(const std::string& title) : title_(title) {
std::println("Created {}", title_);
}
Report(const Report& other) : title_(other.title_ + " (copy)") {
std::println("Copied {}", other.title_);
}
const std::string& title() const { return title_; }
private:
std::string title_;
};
int main() {
Report a("Sales Q3");
Report b = a; // copy constructor runs
std::println("a: {}, b: {}", a.title(), b.title());
}Created Sales Q3 Copied Sales Q3 a: Sales Q3, b: Sales Q3 (copy)
Two special markers control the functions the compiler writes for you:
= defaultasks for the compiler's version, for exampleStudent() = default;to bring back a default constructor.= deleteforbids an operation.Report(const Report&) = delete;makes any attempt to copy aReporta compile error, which suits objects like a database connection that must not be duplicated.
Common mistakes
- Assigning members in the body instead of initialising them. It compiles for most types but fails for
constand reference members, and wastes work. - Forgetting
expliciton single-argument constructors. Numbers and strings then convert into your type without warning. - Writing
Student s();to call the default constructor. This declares a function nameds. WriteStudent s;orStudent s{};. - Listing initialisers in the wrong order. They run in declaration order regardless; match the two orders.
Key points
- A constructor has the class's name and no return type, and runs automatically when an object is created.
- Initialise members in the member initializer list; it is required for
constand reference members. - Writing any constructor removes the automatic default constructor; use
= defaultto bring it back. - Constructors can be overloaded and can delegate to each other; static members are shared by the whole class.
- Mark single-argument constructors
explicit; use= deleteto forbid copying when it makes no sense.
Exercise
Write a class Book with private members title_, author_ and copies_. Give it a constructor taking title and author, with copies defaulting to 1, and an initializer list. Add a delegating constructor that takes only a title and uses "Unknown" as the author. Make the copy constructor = delete. Create three books and print each one's details with a show() member function.
Show solution
#include <print>
#include <string>
class Book {
public:
Book(const std::string& title, const std::string& author, int copies = 1)
: title_(title), author_(author), copies_(copies) {}
explicit Book(const std::string& title) : Book(title, "Unknown") {}
Book(const Book&) = delete;
void show() const {
std::println("{} by {} ({} copies)", title_, author_, copies_);
}
private:
std::string title_;
std::string author_;
int copies_;
};
int main() {
Book a("Godan", "Premchand", 3);
Book b("Malgudi Days", "R. K. Narayan");
Book c("Old Notes");
a.show();
b.show();
c.show();
// Book d = a; // error: copy constructor is deleted
}Godan by Premchand (3 copies) Malgudi Days by R. K. Narayan (1 copies) Old Notes by Unknown (1 copies)
The one-argument constructor delegates to the main one, so the default author and default copy count are set in one place. Printing "1 copies" is a small grammar bug you could fix with a condition.
Quick Quiz
1. What is special about a constructor's declaration?
Constructors are named after the class and have no return type at all.
2. Which members MUST be set in the member initializer list?
const and reference members can only be initialised, never assigned later.
3. What does explicit on a one-argument constructor prevent?
explicit stops the compiler from silently using the constructor as a conversion.
4. What does Student s(); declare?
This is the "most vexing parse"; write Student s; or Student s{}; instead.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
