TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #1091 3.8K
​​Конструктор и инвариант
#новичкам

У нас есть 2 варианта, как мы можем наполнять объект данными.

1️⃣ Делаем все поля публичными, не определяем ни одного конструктор и используем агрегатную инициализацию

struct Order {
std::string client_id;
std::string item_id;
float amount;
};

Example ex = {"123", "1234", 42.0};


2️⃣ Делаем конструктор и заполняем приватные поля в нем

class Clock {
int hour, minute;
public:
Clock(int h, int m) : hour(h), minute(m) {
...
}
};

auto cl = Clock(12, 34)


Как понять, какой вариант выбрать, когда в следующий раз придется писать новый класс? Будем разбираться

Для того, чтобы агрегатная инциализация была корректным способом создания объекта, должны быть выполнены некоторые условия:

👉🏿 Поля должны быть независимы друг от друга.

👉🏿 Поля должны иметь возможность представлять собой весь спектр значений своего типа.

👉🏿 Нет дополнительной логики, которая срабатывает на каждое создание объекта.

Эти 3 пункта гарантируют, что наш класс - это просто набор каких-то значений. Мы можем собрать этот набор частично, поменять содержимое по середине пути и ничего плохого не произойдет.

В противном же случае в вашем классе есть инвариант, который важно сохранять для каждого объекта.

Например, в объекте типа даты, который создается из строки, нужно проверять, валидная ли дата передана. В объекте типа математического интервала левый конец должен быть меньше либо равен правому. Или при создании какого-то объекта мы накручиваем счетчик из метрики.

И вот как раз для того, чтобы вся логика проверки и обеспечения инварианта объекта была внутри кода класса, а не в клиентском коде, нужно определять конструктор. Так детали реализации будут инкапсулированы внутрь класса. Это позволит абстрагироваться от них в клиентском коде и даже если логика инварианта изменится, то это никак не затронет внешний код.

class BufferView {
const char* data;
size_t size;
public:
BufferView(const char* ptr, size_t len) : data(ptr), size(len) {
if (size > 0 && data == nullptr)
throw std::runtime_error("BufferView error: nonzero size while data is nullptr");
}
};


Странно было бы на пользователя возлагать ответственность за проверку соответствия размера и содержимого буфера. Они ведь такие безответственные.

Ну и в будущем, если мы захотим запретить вообще передавать в буффер nullptr, то это изменение коснется только кода класса.

Don't give details to a client. Stay cool.

#design #goodpractice
  • 👍 15
  • ❤ 7
  • 🔥 7
  • 😁 3
  • 🎄 2
  • ❤‍🔥 1
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →