Объекты · Классы и память

Классы

Тип, который сам следит за своей корректностью

~35 мин 6 шагов

После урока ты сможешь

  • Отличать класс с инвариантом от простой структуры
  • Писать конструкторы со списком инициализации
  • Делать методы const, если они ничего не меняют

Шаг 1 из 6

Структура, которая не может себя защитить

Опишем банковский счёт структурой:

struct Account {
    std::string owner;
    long long balance = 0;
};
​
Account acc{"Аня", 1000};
acc.balance = -50000;   // ничто не мешает

У счёта есть правило: баланс не бывает отрицательным. Такое правило, которое должно выполняться всегда, называется инвариантом. В структуре его некому охранять: любой код в любом месте программы может записать в поле что угодно. В проекте на сто тысяч строк найти, кто испортил баланс, почти невозможно.

Класс решает эту проблему: данные закрыты, а менять их можно только через методы, которые проверяют правила.

main.cpp
#include <print>
#include <string>
#include <utility>
​
class Account {
public:
    explicit Account(std::string owner) : owner_(std::move(owner)) {}
​
    bool deposit(long long amount) {
        if (amount <= 0) {
            return false;
        }
        balance_ += amount;
        return true;
    }
​
    bool withdraw(long long amount) {
        if (amount <= 0 || amount > balance_) {
            return false;   // инвариант под защитой
        }
        balance_ -= amount;
        return true;
    }
​
    long long balance() const { return balance_; }
    const std::string& owner() const { return owner_; }
​
private:
    std::string owner_;
    long long balance_ = 0;
};
​
int main() {
    Account acc("Аня");
    acc.deposit(1000);
    std::println("снять 5000: {}", acc.withdraw(5000));
    std::println("снять 300: {}", acc.withdraw(300));
    std::println("{}: {}", acc.owner(), acc.balance());
    // acc.balance_ = -50000;   // ошибка компиляции: поле private
}