Lesson 10 / 25
Classes, Constructors and Destructors
Write classes with member initialiser lists, const member functions and destructors.
User-defined types done well
A class (or struct, which is identical except members are public by default) groups data and functions. Members are private by default in a class; expose a small public interface. Constructors initialise objects; use the member initialiser list (: name_(name), balance_(0)) rather than assignments in the body, because members are initialised before the body runs, const and reference members can only be set there, and it avoids a default construction followed by assignment. Members are initialised in the order they are declared, not the order written in the list, which compilers warn about. Mark single-argument constructors explicit to prevent surprising implicit conversions. Default member initialisers (int count_{0};) set defaults in one place. A destructor (~ClassName()) runs when the object's lifetime ends and releases resources. Mark member functions that do not modify the object const (double balance() const;), so they can be called on const objects and const references. Use = default and = delete to request or forbid special member functions explicitly.
Lifetime of an object
Construction initialises members in declaration order; destruction runs in reverse when the scope ends.
A well-formed class
Initialiser list, explicit constructor, const member functions and invariants.
#include <stdexcept>
#include <string>
#include <utility>
class Account {
public:
explicit Account(std::string owner, long long opening_paise = 0)
: owner_(std::move(owner)), balance_(opening_paise) {
if (balance_ < 0) throw std::invalid_argument("opening balance must be >= 0");
}
void deposit(long long paise) {
if (paise <= 0) throw std::invalid_argument("deposit must be positive");
balance_ += paise;
}
bool withdraw(long long paise) {
if (paise <= 0 || paise > balance_) return false;
balance_ -= paise;
return true;
}
[[nodiscard]] long long balance() const { return balance_; } // const: does not modify
[[nodiscard]] const std::string& owner() const { return owner_; }
private:
std::string owner_;
long long balance_{0};
};
// Account a = "Asha"; // error: constructor is explicit (no accidental conversion)
Account a{"Asha", 50'000};Initialise in declaration order
If balance_ is declared before owner_ but your initialiser list uses owner_ to compute balance_, you read an uninitialised member. Keep the list in the same order as the declarations and enable warnings such as -Wreorder.
Quick check: Why is a member initialiser list preferred over assigning members in the constructor body?
- It is required for all members
- It makes members public
- Members are initialised directly instead of being default-constructed then assigned, and const or reference members require it
- It runs after the constructor body
Answer
Members are initialised directly instead of being default-constructed then assigned, and const or reference members require it — Initialiser lists initialise members once, correctly, before the body runs.