पाठ 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 horizontal timeline with a building block assembling at the start and dismantling at the end, small arrows showing reverse order.
Figure 4.1 — Construction and destruction order.

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.

त्वरित जाँच: 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.